project panel: Improve performance in worktrees with lots of files. (#12980)

When working on a repro for a different issue that involved a worktree
with lots of files (100k to be precise), UI became pretty unresponsive.
I pinned it down to us repeatedly preparing a HashSet of all paths in
the currently-scrolled-to worktree, once per each entry in the range
passed to for_each_visible_range (which is e.g. called during
rendering).

This PR makes that hashing happen just once per worktree. Additionally,
we no longer iterate over (potentially) all entries in a given worktree
when calculating the depth of a given entry.

Note that we could probably be smarter about this still; instead of
recalculating the hashset per each call to for_each_visible_entry, we
could do it whenever we update entries in the project panel. However,
with this PR I wanted to get a quick bang for a small buck; I'm pretty
confident in the change as is, it is relatively straightforward and
messing with worktree updates is more involved.



Release Notes:

- Improvement performance of project panel in large worktrees
This commit is contained in:
Piotr Osiewicz 2024-06-13 14:20:38 +02:00 committed by GitHub
parent 7798f64d1b
commit eb7b5a7131
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1792,6 +1792,10 @@ impl ProjectPanel {
.unwrap_or(&[]);
let entry_range = range.start.saturating_sub(ix)..end_ix - ix;
let entries = visible_worktree_entries
.iter()
.map(|e| (e.path.clone()))
.collect();
for entry in visible_worktree_entries[entry_range].iter() {
let status = git_status_setting.then(|| entry.git_status).flatten();
let is_expanded = expanded_entry_ids.binary_search(&entry.id).is_ok();
@ -1812,10 +1816,8 @@ impl ProjectPanel {
}
};
let (depth, difference) = ProjectPanel::calculate_depth_and_difference(
entry,
visible_worktree_entries,
);
let (depth, difference) =
ProjectPanel::calculate_depth_and_difference(entry, &entries);
let filename = match difference {
diff if diff > 1 => entry
@ -1888,32 +1890,21 @@ impl ProjectPanel {
fn calculate_depth_and_difference(
entry: &Entry,
visible_worktree_entries: &Vec<Entry>,
visible_worktree_entries: &HashSet<Arc<Path>>,
) -> (usize, usize) {
let visible_worktree_paths: HashSet<Arc<Path>> = visible_worktree_entries
.iter()
.map(|e| e.path.clone())
.collect();
let (depth, difference) = entry
.path
.ancestors()
.skip(1) // Skip the entry itself
.find_map(|ancestor| {
if visible_worktree_paths.contains(ancestor) {
let parent_entry = visible_worktree_entries
.iter()
.find(|&e| &*e.path == ancestor)
.unwrap();
if let Some(parent_entry) = visible_worktree_entries.get(ancestor) {
let entry_path_components_count = entry.path.components().count();
let parent_path_components_count = parent_entry.path.components().count();
let parent_path_components_count = parent_entry.components().count();
let difference = entry_path_components_count - parent_path_components_count;
let depth = parent_entry
.path
.ancestors()
.skip(1)
.filter(|ancestor| visible_worktree_paths.contains(*ancestor))
.filter(|ancestor| visible_worktree_entries.contains(*ancestor))
.count();
Some((depth + 1, difference))
} else {