-
-
Notifications
You must be signed in to change notification settings - Fork 308
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Profile TW performance with large task DBs #3618
Comments
I think in order to have it somehow comparable we should use here the existing I am planning to take a look into this, however I will probably not have time before end of September :/ |
Okay, I just noticed that one "counter" or timer was lost due to the transition to the TDB2 backend. More specifically, the timer which tracked how long it takes to load some task set into Taskwarrior. The counter was in Line 419 of TDB2.cpp. Context::getContext ().time_load_us += timer.total_us (); This explains why the reporting of the performance always states |
That flame graph seems to have multiple commands in it -- is it a concatenation from multiple runs? It's a little difficult to interpret the graphic, since things are cut off. Is there a way to share the source file from which that was drawn? #3635 should restore the |
I will come back and see how to extract it better to a simple output. |
Okay, I finally found some time to look a bit back into it. I run the
The results can be seen in the following pdf, however it is a bit messy and mainly shows there the counter of the calls. However, reproducing it following the manual of callgrind is quite straight forward. Long story short, I know to little of the code in TW to properly understand the output or really get an overview which call might be unnecessary or what really limits the performance. I will try to understand and wrap my head around it. On my laptop, using the same databse it takes approx 0,2 s with |
This suggests that most of that time is spent in the Here's the code for rebuilding the working set: /// Rebuild the working set using a function to identify tasks that should be in the set. This
/// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that
/// are not already in the working set but should be. The rebuild occurs in a single
/// trasnsaction against the storage backend.
pub fn rebuild<F>(txn: &mut dyn StorageTxn, in_working_set: F, renumber: bool) -> Result<()>
where
F: Fn(&TaskMap) -> bool,
{
let mut new_ws = vec![None]; // index 0 is always None
let mut seen = HashSet::new();
// The goal here is for existing working-set items to be "compressed' down to index 1, so
// we begin by scanning the current working set and inserting any tasks that should still
// be in the set into new_ws, implicitly dropping any tasks that are no longer in the
// working set.
for elt in txn.get_working_set()?.drain(1..) {
if let Some(uuid) = elt {
if let Some(task) = txn.get_task(uuid)? {
if in_working_set(&task) {
new_ws.push(Some(uuid));
seen.insert(uuid);
continue;
}
}
}
// if we are not renumbering, then insert a blank working-set entry here
if !renumber {
new_ws.push(None);
}
}
// if renumbering, clear the working set and re-add
if renumber {
txn.clear_working_set()?;
for elt in new_ws.drain(1..new_ws.len()).flatten() {
txn.add_to_working_set(elt)?;
}
} else {
// ..otherwise, just clear the None items determined above from the working set
for (i, elt) in new_ws.iter().enumerate().skip(1) {
if elt.is_none() {
txn.set_working_set_item(i, None)?;
}
}
}
// Now go hunting for tasks that should be in this list but are not, adding them at the
// end of the list, whether renumbering or not
for (uuid, task) in txn.all_tasks()? {
if !seen.contains(&uuid) && in_working_set(&task) {
txn.add_to_working_set(uuid)?;
}
}
txn.commit()?;
Ok(())
} Drilling down into the working set rebuild, the majority of the time is in Going back to Lines 267 to 286 in 01ced32
A few thoughts off the top of my head:
What do you think? |
...Probably running a profiler is the action item from this issue...
Originally posted by @djmitche in #3329 (comment)
I think this would be best done from Taskwarrior, treating the Rust code as a "black box". It would be great to learn which calls into Rust are taking the longest -- and what non-Rust things are taking a long time, too. I can think of a few possibilities we might learn, but the truth is probably something different:
task list
#3418)TDB2::pending_tasks
)std::string
and Rust'sString
are not compatible)I don't know much about profiling in C++, but perhaps you, dear reader, do?
The text was updated successfully, but these errors were encountered: