From 1d868e19f28ba6c6858790284b66f4efc8116388 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Tue, 27 Aug 2024 18:37:26 +0200 Subject: [PATCH] project search: Render results in batches (#16960) This improves performance, because we don't render after every single match/range that was added to the results. I think for my example search it's twice as fast? ## Numbers Recorded in debug mode (because it's 6:30pm and my poor computer has spun its fan enough for today and also because you can see the change of the effect more in debug mode), rendering `<` searched in `zed.dev` repo: - Before: `14.59558225s` - After: `2.604320875s` ## Videos Before (recorded in release mode): https://github.com/user-attachments/assets/909260fa-3e69-49ab-8786-dd384e2a27ee After (recorded in release mode): https://github.com/user-attachments/assets/fc8a85d3-e575-470f-b59c-16a6df8b3f80 ## Release Notes Release Notes: - Improved performance of rendering project-search results in the multi-buffer after finding them. --- crates/search/src/project_search.rs | 72 ++++++++++++++++++----------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 6003d4dcd3..37b477e66d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -221,36 +221,56 @@ impl ProjectSearch { let mut limit_reached = false; while let Some(results) = matches.next().await { - for result in results { - match result { - project::search::SearchResult::Buffer { buffer, ranges } => { - let mut match_ranges = this - .update(&mut cx, |this, cx| { - this.excerpts.update(cx, |excerpts, cx| { - excerpts.stream_excerpts_with_context_lines( - buffer, - ranges, - editor::DEFAULT_MULTIBUFFER_CONTEXT, - cx, - ) - }) - }) - .ok()?; + let tasks = results + .into_iter() + .map(|result| { + let this = this.clone(); - while let Some(range) = match_ranges.next().await { - this.update(&mut cx, |this, _| { - this.no_results = Some(false); - this.match_ranges.push(range) - }) - .ok()?; + cx.spawn(|mut cx| async move { + match result { + project::search::SearchResult::Buffer { buffer, ranges } => { + let mut match_ranges_rx = + this.update(&mut cx, |this, cx| { + this.excerpts.update(cx, |excerpts, cx| { + excerpts.stream_excerpts_with_context_lines( + buffer, + ranges, + editor::DEFAULT_MULTIBUFFER_CONTEXT, + cx, + ) + }) + })?; + + let mut match_ranges = vec![]; + while let Some(range) = match_ranges_rx.next().await { + match_ranges.push(range); + } + anyhow::Ok((match_ranges, false)) + } + project::search::SearchResult::LimitReached => { + anyhow::Ok((vec![], true)) + } } - this.update(&mut cx, |_, cx| cx.notify()).ok()?; - } - project::search::SearchResult::LimitReached => { - limit_reached = true; - } + }) + }) + .collect::>(); + + let result_ranges = futures::future::join_all(tasks).await; + let mut combined_ranges = vec![]; + for (ranges, result_limit_reached) in result_ranges.into_iter().flatten() { + combined_ranges.extend(ranges); + if result_limit_reached { + limit_reached = result_limit_reached; } } + this.update(&mut cx, |this, cx| { + if !combined_ranges.is_empty() { + this.no_results = Some(false); + this.match_ranges.extend(combined_ranges); + cx.notify(); + } + }) + .ok()?; } this.update(&mut cx, |this, cx| {