Avoid extra `smol::channel` when iterating through snapshot paths

Antonio Scandurra created

Change summary

crates/project/src/project.rs  | 125 +++++++++++++++++++----------------
crates/project/src/worktree.rs |   8 +-
2 files changed, 72 insertions(+), 61 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -28,6 +28,7 @@ use sha2::{Digest, Sha256};
 use smol::block_on;
 use std::{
     cell::RefCell,
+    cmp,
     convert::TryInto,
     hash::Hash,
     mem,
@@ -2050,33 +2051,18 @@ impl Project {
         cx: &mut ModelContext<Self>,
     ) -> Task<HashMap<ModelHandle<Buffer>, Vec<Range<Anchor>>>> {
         if self.is_local() {
-            let (paths_to_search_tx, paths_to_search_rx) = smol::channel::bounded(1024);
-
             let snapshots = self
                 .strong_worktrees(cx)
                 .filter_map(|tree| {
                     let tree = tree.read(cx).as_local()?;
-                    Some((tree.abs_path().clone(), tree.snapshot()))
+                    Some(tree.snapshot())
                 })
                 .collect::<Vec<_>>();
-            cx.background()
-                .spawn(async move {
-                    for (snapshot_abs_path, snapshot) in snapshots {
-                        for file in snapshot.files(false, 0) {
-                            if paths_to_search_tx
-                                .send((snapshot.id(), snapshot_abs_path.clone(), file.path.clone()))
-                                .await
-                                .is_err()
-                            {
-                                return;
-                            }
-                        }
-                    }
-                })
-                .detach();
 
+            let background = cx.background().clone();
+            let path_count: usize = snapshots.iter().map(|s| s.visible_file_count()).sum();
+            let workers = background.num_cpus().min(path_count);
             let (matching_paths_tx, mut matching_paths_rx) = smol::channel::bounded(1024);
-            let workers = cx.background().num_cpus();
             cx.background()
                 .spawn({
                     let fs = self.fs.clone();
@@ -2086,41 +2072,64 @@ impl Project {
                         let fs = &fs;
                         let query = &query;
                         let matching_paths_tx = &matching_paths_tx;
+                        let paths_per_worker = (path_count + workers - 1) / workers;
+                        let snapshots = &snapshots;
                         background
                             .scoped(|scope| {
-                                for _ in 0..workers {
-                                    let mut paths_to_search_rx = paths_to_search_rx.clone();
+                                for worker_ix in 0..workers {
+                                    let worker_start_ix = worker_ix * paths_per_worker;
+                                    let worker_end_ix = worker_start_ix + paths_per_worker;
                                     scope.spawn(async move {
-                                        let mut path = PathBuf::new();
-                                        while let Some((
-                                            worktree_id,
-                                            snapshot_abs_path,
-                                            file_path,
-                                        )) = paths_to_search_rx.next().await
-                                        {
-                                            if matching_paths_tx.is_closed() {
+                                        let mut snapshot_start_ix = 0;
+                                        let mut abs_path = PathBuf::new();
+                                        for snapshot in snapshots {
+                                            let snapshot_end_ix =
+                                                snapshot_start_ix + snapshot.visible_file_count();
+                                            if worker_end_ix <= snapshot_start_ix {
                                                 break;
-                                            }
-
-                                            path.clear();
-                                            path.push(&snapshot_abs_path);
-                                            path.push(&file_path);
-                                            let matches = if let Some(file) =
-                                                fs.open_sync(&path).await.log_err()
-                                            {
-                                                query.detect(file).unwrap_or(false)
+                                            } else if worker_start_ix > snapshot_end_ix {
+                                                snapshot_start_ix = snapshot_end_ix;
+                                                continue;
                                             } else {
-                                                false
-                                            };
-
-                                            if matches {
-                                                if matching_paths_tx
-                                                    .send((worktree_id, file_path))
-                                                    .await
-                                                    .is_err()
+                                                let start_in_snapshot = worker_start_ix
+                                                    .saturating_sub(snapshot_start_ix);
+                                                let end_in_snapshot =
+                                                    cmp::min(worker_end_ix, snapshot_end_ix)
+                                                        - snapshot_start_ix;
+
+                                                for entry in snapshot
+                                                    .files(false, start_in_snapshot)
+                                                    .take(end_in_snapshot - start_in_snapshot)
                                                 {
-                                                    break;
+                                                    if matching_paths_tx.is_closed() {
+                                                        break;
+                                                    }
+
+                                                    abs_path.clear();
+                                                    abs_path.push(&snapshot.abs_path());
+                                                    abs_path.push(&entry.path);
+                                                    let matches = if let Some(file) =
+                                                        fs.open_sync(&abs_path).await.log_err()
+                                                    {
+                                                        query.detect(file).unwrap_or(false)
+                                                    } else {
+                                                        false
+                                                    };
+
+                                                    if matches {
+                                                        let project_path =
+                                                            (snapshot.id(), entry.path.clone());
+                                                        if matching_paths_tx
+                                                            .send(project_path)
+                                                            .await
+                                                            .is_err()
+                                                        {
+                                                            break;
+                                                        }
+                                                    }
                                                 }
+
+                                                snapshot_start_ix = snapshot_end_ix;
                                             }
                                         }
                                     });
@@ -2175,14 +2184,16 @@ impl Project {
                             let mut buffers_rx = buffers_rx.clone();
                             scope.spawn(async move {
                                 while let Some((buffer, snapshot)) = buffers_rx.next().await {
-                                    for range in query.search(snapshot.as_rope()).await {
-                                        let range = snapshot.anchor_before(range.start)
-                                            ..snapshot.anchor_after(range.end);
-                                        worker_matched_buffers
-                                            .entry(buffer.clone())
-                                            .or_insert(Vec::new())
-                                            .push(range);
-                                    }
+                                    let buffer_matches = query
+                                        .search(snapshot.as_rope())
+                                        .await
+                                        .iter()
+                                        .map(|range| {
+                                            snapshot.anchor_before(range.start)
+                                                ..snapshot.anchor_after(range.end)
+                                        })
+                                        .collect();
+                                    worker_matched_buffers.insert(buffer.clone(), buffer_matches);
                                 }
                             });
                         }
@@ -4888,7 +4899,7 @@ mod tests {
             .await;
 
         assert_eq!(
-            search(&project, SearchQuery::text("TWO", false, false), &mut cx).await,
+            search(&project, SearchQuery::text("TWO", false, true), &mut cx).await,
             HashMap::from_iter([
                 ("two.rs".to_string(), vec![6..9]),
                 ("three.rs".to_string(), vec![37..40])
@@ -4906,7 +4917,7 @@ mod tests {
         });
 
         assert_eq!(
-            search(&project, SearchQuery::text("TWO", false, false), &mut cx).await,
+            search(&project, SearchQuery::text("TWO", false, true), &mut cx).await,
             HashMap::from_iter([
                 ("two.rs".to_string(), vec![6..9]),
                 ("three.rs".to_string(), vec![37..40]),

crates/project/src/worktree.rs 🔗

@@ -554,10 +554,6 @@ impl LocalWorktree {
         Ok((tree, scan_states_tx))
     }
 
-    pub fn abs_path(&self) -> &Arc<Path> {
-        &self.abs_path
-    }
-
     pub fn contains_abs_path(&self, path: &Path) -> bool {
         path.starts_with(&self.abs_path)
     }
@@ -1017,6 +1013,10 @@ impl Snapshot {
 }
 
 impl LocalSnapshot {
+    pub fn abs_path(&self) -> &Arc<Path> {
+        &self.abs_path
+    }
+
     #[cfg(test)]
     pub(crate) fn to_proto(
         &self,