Improve project search performance (#20211)
Follow-up of https://github.com/zed-industries/zed/pull/20171 Reduces time Zed needs to reach maximum search results by an order of a magnitude. Methodology: * plugged-in mac with Instruments and Zed open * Zed is restarted before each measurement, `zed` project is opened, a *.rs file is opened and rust-analyzer is fully loaded, file is closed then * from an "empty" state, a `test` word is searched in the project search * each version is checked with project panel; and then, separately, without it * after we reach maximum test results (the counter stops at `10191+`), the measurement stops Zed Dev is compiled and installed with `./script/bundle-mac -li` ------------------------ [measurements.trace.zip](https://github.com/user-attachments/files/17625516/measurements.trace.zip) Before: * Zed Nightly with outline panel open <img width="1113" alt="image" src="https://github.com/user-attachments/assets/62b29a69-c266-4d46-8c3c-0e9534ca7967"> Took over 30s to load the result set * Zed Nightly without outline panel <img width="1109" alt="image" src="https://github.com/user-attachments/assets/82d8d9d6-e8f2-4e67-af55-3f54a7c1d92d"> Took over 24s to load the result set * Zed Dev with outline panel open <img width="1131" alt="image" src="https://github.com/user-attachments/assets/15605ff8-0787-428e-bbb6-f8496f7e1d43"> Took around 6s to load the result set (the profile was running a bit longer) * Zed Dev without outline panel <img width="1147" alt="image" src="https://github.com/user-attachments/assets/0715d73e-f41a-4d74-a604-a3a96ad8d585"> Took around 5s to load the result set --------------------- Improvements in the outline panel: * https://github.com/zed-industries/zed/pull/20171 ensured we reuse previous rendered search results from the outline panel * all search results are now rendered in the background thread * only the entries that are rendered with gpui are sent to the background thread for rendering * FS entries' update logic does nothing before the debounce now Improvements in the editor: * cursor update operations are debounced and all calculations start after the debounce only * linked edits are now debounced and all work is done after the debounce only Further possible improvements: * we could batch calculations of text coordinates, related to the search entries: right now, each search match range is expanded around and clipped, then fitted to the closest surrounding whitespace (if any, otherwise it's just trimmed). Each such calculation requires multiple tree traversals, which is suboptimal and causes more CPU usage than we could use. * linked edits are always calculated, even if the language settings have it disabled, or the corresponding language having no corresponding capabilities Release Notes: - Improve large project search performance
This commit is contained in:
parent
81dd4ca1c9
commit
3856599853
4 changed files with 330 additions and 244 deletions
|
@ -1,9 +1,9 @@
|
|||
use editor::{Editor, ToPoint};
|
||||
use gpui::{AppContext, Subscription, View, WeakView};
|
||||
use gpui::{AppContext, Subscription, Task, View, WeakView};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use std::fmt::Write;
|
||||
use std::{fmt::Write, time::Duration};
|
||||
use text::{Point, Selection};
|
||||
use ui::{
|
||||
div, Button, ButtonCommon, Clickable, FluentBuilder, IntoElement, LabelSize, ParentElement,
|
||||
|
@ -23,6 +23,7 @@ pub struct CursorPosition {
|
|||
position: Option<Point>,
|
||||
selected_count: SelectionStats,
|
||||
workspace: WeakView<Workspace>,
|
||||
update_position: Task<()>,
|
||||
_observe_active_editor: Option<Subscription>,
|
||||
}
|
||||
|
||||
|
@ -32,40 +33,61 @@ impl CursorPosition {
|
|||
position: None,
|
||||
selected_count: Default::default(),
|
||||
workspace: workspace.weak_handle(),
|
||||
update_position: Task::ready(()),
|
||||
_observe_active_editor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
fn update_position(
|
||||
&mut self,
|
||||
editor: View<Editor>,
|
||||
debounce: Option<Duration>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let editor = editor.downgrade();
|
||||
self.update_position = cx.spawn(|cursor_position, mut cx| async move {
|
||||
if let Some(debounce) = debounce {
|
||||
cx.background_executor().timer(debounce).await;
|
||||
}
|
||||
|
||||
self.selected_count = Default::default();
|
||||
self.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection: Option<Selection<usize>> = None;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
self.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
self.selected_count.lines += (selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
self.selected_count.lines += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.position = last_selection.map(|s| s.head().to_point(&buffer));
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
cursor_position.update(cx, |cursor_position, cx| {
|
||||
cursor_position.selected_count = SelectionStats::default();
|
||||
cursor_position.selected_count.selections = editor.selections.count();
|
||||
let mut last_selection = None::<Selection<usize>>;
|
||||
for selection in editor.selections.all::<usize>(cx) {
|
||||
cursor_position.selected_count.characters += buffer
|
||||
.text_for_range(selection.start..selection.end)
|
||||
.map(|t| t.chars().count())
|
||||
.sum::<usize>();
|
||||
if last_selection
|
||||
.as_ref()
|
||||
.map_or(true, |last_selection| selection.id > last_selection.id)
|
||||
{
|
||||
last_selection = Some(selection);
|
||||
}
|
||||
}
|
||||
for selection in editor.selections.all::<Point>(cx) {
|
||||
if selection.end != selection.start {
|
||||
cursor_position.selected_count.lines +=
|
||||
(selection.end.row - selection.start.row) as usize;
|
||||
if selection.end.column != 0 {
|
||||
cursor_position.selected_count.lines += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
cursor_position.position =
|
||||
last_selection.map(|s| s.head().to_point(&buffer));
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
.transpose()
|
||||
.ok()
|
||||
.flatten();
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn write_position(&self, text: &mut String, cx: &AppContext) {
|
||||
|
@ -154,6 +176,8 @@ impl Render for CursorPosition {
|
|||
}
|
||||
}
|
||||
|
||||
const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
|
||||
|
||||
impl StatusItemView for CursorPosition {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
|
@ -161,8 +185,11 @@ impl StatusItemView for CursorPosition {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
|
||||
self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
|
||||
self.update_position(editor, cx);
|
||||
self._observe_active_editor =
|
||||
Some(cx.observe(&editor, |cursor_position, editor, cx| {
|
||||
Self::update_position(cursor_position, editor, Some(UPDATE_DEBOUNCE), cx)
|
||||
}));
|
||||
self.update_position(editor, None, cx);
|
||||
} else {
|
||||
self.position = None;
|
||||
self._observe_active_editor = None;
|
||||
|
|
|
@ -229,7 +229,7 @@ mod tests {
|
|||
use indoc::indoc;
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -379,6 +379,7 @@ mod tests {
|
|||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert_eq!(
|
||||
&SelectionStats {
|
||||
|
@ -397,6 +398,7 @@ mod tests {
|
|||
);
|
||||
});
|
||||
editor.update(cx, |editor, cx| editor.select_all(&SelectAll, cx));
|
||||
cx.executor().advance_clock(Duration::from_millis(200));
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
assert_eq!(
|
||||
&SelectionStats {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue