From eb7b5a7131fc1f693ceff8c314f3d3dae5e6c85c Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:20:38 +0200 Subject: [PATCH] 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 --- crates/project_panel/src/project_panel.rs | 29 ++++++++--------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e29616fa37..4749d45a2e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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, + visible_worktree_entries: &HashSet>, ) -> (usize, usize) { - let visible_worktree_paths: HashSet> = 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 {