Replace easy-parallel with scoped-pool for path searches

Nathan Sobo and Brooks Swinnerton created

The easy-parallel crate spawned new threads on each call, which was resulting in way too many threads.

Co-Authored-By: Brooks Swinnerton <934497+bswinnerton@users.noreply.github.com>

Change summary

Cargo.lock                   | 32 +++++++++++++++++++++++++++++++-
gpui/Cargo.toml              |  1 +
gpui/src/app.rs              |  6 ++++++
gpui/src/lib.rs              |  1 +
zed/src/file_finder.rs       |  3 ++-
zed/src/worktree/fuzzy.rs    | 16 ++++++++--------
zed/src/worktree/worktree.rs |  6 ++++--
7 files changed, 53 insertions(+), 12 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -498,6 +498,12 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "crossbeam"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be"
+
 [[package]]
 name = "crossbeam-channel"
 version = "0.4.4"
@@ -924,6 +930,7 @@ dependencies = [
  "rand 0.8.3",
  "replace_with",
  "resvg",
+ "scoped-pool",
  "serde",
  "serde_json",
  "simplelog",
@@ -1065,7 +1072,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
 dependencies = [
- "scopeguard",
+ "scopeguard 1.1.0",
 ]
 
 [[package]]
@@ -1674,12 +1681,29 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "scoped-pool"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "817a3a15e704545ce59ed2b5c60a5d32bda4d7869befb8b36667b658a6c00b43"
+dependencies = [
+ "crossbeam",
+ "scopeguard 0.1.2",
+ "variance",
+]
+
 [[package]]
 name = "scoped-tls"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
 
+[[package]]
+name = "scopeguard"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a076157c1e2dc561d8de585151ee6965d910dd4dcb5dabb7ae3e83981a6c57"
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -2058,6 +2082,12 @@ dependencies = [
  "ctor",
 ]
 
+[[package]]
+name = "variance"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3abfc2be1fb59663871379ea884fd81de80c496f2274e021c01d6fe56cd77b05"
+
 [[package]]
 name = "vec-arena"
 version = "1.0.0"

gpui/Cargo.toml 🔗

@@ -18,6 +18,7 @@ pathfinder_geometry = "0.5"
 rand = "0.8.3"
 replace_with = "0.1.7"
 resvg = "0.14"
+scoped-pool = "1.0.0"
 serde = "1.0.125"
 serde_json = "1.0.64"
 smallvec = "1.6.1"

gpui/src/app.rs 🔗

@@ -413,6 +413,7 @@ impl MutableAppContext {
                 windows: HashMap::new(),
                 ref_counts: Arc::new(Mutex::new(RefCounts::default())),
                 background: Arc::new(executor::Background::new()),
+                scoped_pool: scoped_pool::Pool::new(num_cpus::get()),
             },
             actions: HashMap::new(),
             global_actions: HashMap::new(),
@@ -1315,6 +1316,7 @@ pub struct AppContext {
     windows: HashMap<usize, Window>,
     background: Arc<executor::Background>,
     ref_counts: Arc<Mutex<RefCounts>>,
+    scoped_pool: scoped_pool::Pool,
 }
 
 impl AppContext {
@@ -1353,6 +1355,10 @@ impl AppContext {
     pub fn background_executor(&self) -> &Arc<executor::Background> {
         &self.background
     }
+
+    pub fn scoped_pool(&self) -> &scoped_pool::Pool {
+        &self.scoped_pool
+    }
 }
 
 impl ReadModel for AppContext {

gpui/src/lib.rs 🔗

@@ -27,3 +27,4 @@ pub use presenter::{
     AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
     SizeConstraint, Vector2FExt,
 };
+pub use scoped_pool;

zed/src/file_finder.rs 🔗

@@ -347,8 +347,9 @@ impl FileFinder {
     fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
         let worktrees = self.worktrees(ctx.as_ref());
         let search_id = util::post_inc(&mut self.search_count);
+        let pool = ctx.app().scoped_pool().clone();
         let task = ctx.background_executor().spawn(async move {
-            let matches = match_paths(worktrees.as_slice(), &query, false, false, 100);
+            let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool);
             (search_id, matches)
         });
 

zed/src/worktree/fuzzy.rs 🔗

@@ -1,4 +1,4 @@
-use easy_parallel::Parallel;
+use gpui::scoped_pool;
 
 use super::char_bag::CharBag;
 
@@ -54,6 +54,7 @@ pub fn match_paths(
     include_ignored: bool,
     smart_case: bool,
     max_results: usize,
+    pool: scoped_pool::Pool,
 ) -> Vec<PathMatch> {
     let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
     let query = query.chars().collect::<Vec<_>>();
@@ -68,10 +69,9 @@ pub fn match_paths(
     let segment_size = (path_count + cpus - 1) / cpus;
     let mut segment_results = (0..cpus).map(|_| BinaryHeap::new()).collect::<Vec<_>>();
 
-    Parallel::new()
-        .each(
-            segment_results.iter_mut().enumerate(),
-            |(segment_idx, results)| {
+    pool.scoped(|scope| {
+        for (segment_idx, results) in segment_results.iter_mut().enumerate() {
+            scope.execute(move || {
                 let segment_start = segment_idx * segment_size;
                 let segment_end = segment_start + segment_size;
 
@@ -115,9 +115,9 @@ pub fn match_paths(
                     }
                     tree_start = tree_end;
                 }
-            },
-        )
-        .run();
+            })
+        }
+    });
 
     let mut results = segment_results
         .into_iter()

zed/src/worktree/worktree.rs 🔗

@@ -11,7 +11,7 @@ use crate::{
 use anyhow::{anyhow, Result};
 use crossbeam_channel as channel;
 use easy_parallel::Parallel;
-use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
+use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
 use ignore::dir::{Ignore, IgnoreBuilder};
 use parking_lot::RwLock;
 use smol::prelude::*;
@@ -606,6 +606,7 @@ pub fn match_paths(
     include_ignored: bool,
     smart_case: bool,
     max_results: usize,
+    pool: scoped_pool::Pool,
 ) -> Vec<PathMatch> {
     let tree_states = trees.iter().map(|tree| tree.0.read()).collect::<Vec<_>>();
     fuzzy::match_paths(
@@ -634,6 +635,7 @@ pub fn match_paths(
         include_ignored,
         smart_case,
         max_results,
+        pool,
     )
 }
 
@@ -674,7 +676,7 @@ mod test {
             app.read(|ctx| {
                 let tree = tree.read(ctx);
                 assert_eq!(tree.file_count(), 4);
-                let results = match_paths(&[tree.clone()], "bna", false, false, 10)
+                let results = match_paths(&[tree.clone()], "bna", false, false, 10, ctx.scoped_pool().clone())
                     .iter()
                     .map(|result| tree.entry_path(result.entry_id))
                     .collect::<Result<Vec<PathBuf>, _>>()