## Context While looking into: #32051 and #16120 with instruments, I noticed that `TabSnapshot::to_tab_point` and `TabSnapshot::to_fold_point` are a common bottleneck between the two issues. This PR takes the first steps into closing the stated issues by improving the performance of both those functions. ### Method `to_tab_point` and `to_fold_point` iterate through each character in their rows to find tab characters and translate those characters into their respective transformations. This PR changes this iteration to take advantage of the tab character bitmap in the `Rope` data structure and goes directly to each tab character when iterating. The tab bitmap is now passed from each layer in-between the `Rope` to the `TabMap`. ### Testing I added several randomized tests to ensure that the new `to_tab_point` and `to_fold_point` functions have the same behavior as the old methods they're replacing. I also added `test_random_chunk_bitmap` on each layer the tab bitmap is passed up to the `TabMap` to make sure that the bitmap being passed is transformed correctly between the layers of `DisplayMap`. `test_random_chunk_bitmap` was added to these layers: - buffer - multi buffer - custom_highlights - inlay_map - fold_map ## Benchmarking I setup benchmarks with criterion that is runnable via `cargo bench -p editor --profile=release-fast`. When benchmarking I had my laptop plugged in and did so from the terminal with a minimal amount of processes running. I'm also on a m4 max ### Results #### To Tab Point Went from completing 6.8M iterations in 5s with an average time of `736.13 ns` to `683.38 ns` which is a `-7.1875%` improvement #### To Fold Point Went from completing 6.8M iterations in 5s with an average time of `736.55 ns` to `682.40 ns` which is a `-7.1659%` improvement #### Editor render Went from having an average render time of `62.561 µs` to `57.216 µs` which is a `-8.8248%` improvement #### Build Buffer with one long line Went from having an average buffer build time of `3.2549 ms` to `3.2635 ms` which is a `+0.2151%` regression within the margin of error #### Editor with 1000 multi cursor input Went from having an average edit time of `133.05 ms` to `122.96 ms` which is a `-7.5776%` improvement Release Notes: - N/A --------- Co-authored-by: Remco Smits <djsmits12@gmail.com> Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
173 lines
5.5 KiB
Rust
173 lines
5.5 KiB
Rust
use criterion::{Bencher, BenchmarkId};
|
|
use editor::{
|
|
Editor, EditorMode, MultiBuffer,
|
|
actions::{DeleteToPreviousWordStart, SelectAll, SplitSelectionIntoLines},
|
|
};
|
|
use gpui::{AppContext, Focusable as _, TestAppContext, TestDispatcher};
|
|
use project::Project;
|
|
use rand::{Rng as _, SeedableRng as _, rngs::StdRng};
|
|
use settings::SettingsStore;
|
|
use ui::IntoElement;
|
|
use util::RandomCharIter;
|
|
|
|
fn editor_input_with_1000_cursors(bencher: &mut Bencher<'_>, cx: &TestAppContext) {
|
|
let mut cx = cx.clone();
|
|
let text = String::from_iter(["line:\n"; 1000]);
|
|
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
|
|
|
|
let cx = cx.add_empty_window();
|
|
let editor = cx.update(|window, cx| {
|
|
let editor = cx.new(|cx| {
|
|
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
|
|
editor.set_style(editor::EditorStyle::default(), window, cx);
|
|
editor.select_all(&SelectAll, window, cx);
|
|
editor.split_selection_into_lines(
|
|
&SplitSelectionIntoLines {
|
|
keep_selections: true,
|
|
},
|
|
window,
|
|
cx,
|
|
);
|
|
editor
|
|
});
|
|
window.focus(&editor.focus_handle(cx));
|
|
editor
|
|
});
|
|
|
|
bencher.iter(|| {
|
|
cx.update(|window, cx| {
|
|
editor.update(cx, |editor, cx| {
|
|
editor.handle_input("hello world", window, cx);
|
|
editor.delete_to_previous_word_start(
|
|
&DeleteToPreviousWordStart {
|
|
ignore_newlines: false,
|
|
ignore_brackets: false,
|
|
},
|
|
window,
|
|
cx,
|
|
);
|
|
editor.delete_to_previous_word_start(
|
|
&DeleteToPreviousWordStart {
|
|
ignore_newlines: false,
|
|
ignore_brackets: false,
|
|
},
|
|
window,
|
|
cx,
|
|
);
|
|
});
|
|
})
|
|
});
|
|
}
|
|
|
|
fn open_editor_with_one_long_line(bencher: &mut Bencher<'_>, args: &(String, TestAppContext)) {
|
|
let (text, cx) = args;
|
|
let mut cx = cx.clone();
|
|
|
|
bencher.iter(|| {
|
|
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
|
|
|
|
let cx = cx.add_empty_window();
|
|
let _ = cx.update(|window, cx| {
|
|
let editor = cx.new(|cx| {
|
|
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
|
|
editor.set_style(editor::EditorStyle::default(), window, cx);
|
|
editor
|
|
});
|
|
window.focus(&editor.focus_handle(cx));
|
|
editor
|
|
});
|
|
});
|
|
}
|
|
|
|
fn editor_render(bencher: &mut Bencher<'_>, cx: &TestAppContext) {
|
|
let mut cx = cx.clone();
|
|
let buffer = cx.update(|cx| {
|
|
let mut rng = StdRng::seed_from_u64(1);
|
|
let text_len = rng.random_range(10000..90000);
|
|
if rng.random() {
|
|
let text = RandomCharIter::new(&mut rng)
|
|
.take(text_len)
|
|
.collect::<String>();
|
|
MultiBuffer::build_simple(&text, cx)
|
|
} else {
|
|
MultiBuffer::build_random(&mut rng, cx)
|
|
}
|
|
});
|
|
|
|
let cx = cx.add_empty_window();
|
|
let editor = cx.update(|window, cx| {
|
|
let editor = cx.new(|cx| {
|
|
let mut editor = Editor::new(EditorMode::full(), buffer, None, window, cx);
|
|
editor.set_style(editor::EditorStyle::default(), window, cx);
|
|
editor
|
|
});
|
|
window.focus(&editor.focus_handle(cx));
|
|
editor
|
|
});
|
|
|
|
bencher.iter(|| {
|
|
cx.update(|window, cx| {
|
|
// editor.update(cx, |editor, cx| editor.move_down(&MoveDown, window, cx));
|
|
let mut view = editor.clone().into_any_element();
|
|
let _ = view.request_layout(window, cx);
|
|
let _ = view.prepaint(window, cx);
|
|
view.paint(window, cx);
|
|
});
|
|
})
|
|
}
|
|
|
|
pub fn benches() {
|
|
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(1));
|
|
let cx = gpui::TestAppContext::build(dispatcher, None);
|
|
cx.update(|cx| {
|
|
let store = SettingsStore::test(cx);
|
|
cx.set_global(store);
|
|
assets::Assets.load_test_fonts(cx);
|
|
theme::init(theme::LoadThemes::JustBase, cx);
|
|
// release_channel::init(SemanticVersion::default(), cx);
|
|
client::init_settings(cx);
|
|
language::init(cx);
|
|
workspace::init_settings(cx);
|
|
Project::init_settings(cx);
|
|
editor::init(cx);
|
|
});
|
|
|
|
let mut criterion: criterion::Criterion<_> =
|
|
(criterion::Criterion::default()).configure_from_args();
|
|
|
|
// setup app context
|
|
let mut group = criterion.benchmark_group("Time to render");
|
|
group.bench_with_input(
|
|
BenchmarkId::new("editor_render", "TestAppContext"),
|
|
&cx,
|
|
editor_render,
|
|
);
|
|
|
|
group.finish();
|
|
|
|
let text = String::from_iter(["char"; 1000]);
|
|
let mut group = criterion.benchmark_group("Build buffer with one long line");
|
|
group.bench_with_input(
|
|
BenchmarkId::new("editor_with_one_long_line", "(String, TestAppContext )"),
|
|
&(text, cx.clone()),
|
|
open_editor_with_one_long_line,
|
|
);
|
|
|
|
group.finish();
|
|
|
|
let mut group = criterion.benchmark_group("multi cursor edits");
|
|
group.bench_with_input(
|
|
BenchmarkId::new("editor_input_with_1000_cursors", "TestAppContext"),
|
|
&cx,
|
|
editor_input_with_1000_cursors,
|
|
);
|
|
group.finish();
|
|
}
|
|
|
|
fn main() {
|
|
benches();
|
|
criterion::Criterion::default()
|
|
.configure_from_args()
|
|
.final_summary();
|
|
}
|