Improve performance of create_highlight_endpoints

This commit is contained in:
Lukas Wirth 2026-05-29 23:43:47 +02:00
parent b7b1d1a2c7
commit 60e94218b2
3 changed files with 211 additions and 52 deletions

View file

@ -1,10 +1,12 @@
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use editor::MultiBuffer;
use gpui::TestDispatcher;
use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main};
use editor::{MultiBuffer, display_map::*};
use gpui::{AppContext as _, HighlightStyle, Hsla, TestDispatcher, font, px};
use itertools::Itertools;
use multi_buffer::MultiBufferOffset;
use project::project_settings::DiagnosticSeverity;
use rand::{Rng, SeedableRng, rngs::StdRng};
use std::num::NonZeroU32;
use settings::SettingsStore;
use std::{num::NonZeroU32, time::Duration};
use text::Bias;
use util::RandomCharIter;
@ -101,5 +103,112 @@ fn to_fold_point_benchmark(c: &mut Criterion) {
group.finish();
}
criterion_group!(benches, to_tab_point_benchmark, to_fold_point_benchmark);
fn create_highlight_endpoints_benchmark(c: &mut Criterion) {
const LINE_COUNT: usize = 20_000;
const LINE_VIEW_PORT_COUNT: usize = 100;
const HIGHLIGHTS_PER_LINE: usize = 4;
let dispatcher = TestDispatcher::new(1);
let mut cx = gpui::TestAppContext::build(dispatcher, None);
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
editor::init(cx);
});
let mut text = String::new();
let mut highlight_ranges = Vec::with_capacity(LINE_COUNT * HIGHLIGHTS_PER_LINE);
for line in 0..LINE_COUNT {
text.push_str("fn item_");
text.push_str(&format!("{line:05}"));
text.push_str("() { ");
let start = text.len();
text.push_str("alpha_highlight");
highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));
text.push_str(" + ");
let start = text.len();
text.push_str("beta_highlight");
highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));
text.push_str(" + ");
let start = text.len();
text.push_str("gamma_highlight");
highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));
text.push_str(" + ");
let start = text.len();
text.push_str("delta_highlight");
highlight_ranges.push(MultiBufferOffset(start)..MultiBufferOffset(text.len()));
text.push_str("; }\n");
}
let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
let buffer_snapshot = cx.read(|cx| buffer.read(cx).snapshot(cx));
let highlight_ranges = highlight_ranges
.into_iter()
.map(|range| {
buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_before(range.end)
})
.collect();
let map = cx.new(|cx| {
DisplayMap::new(
buffer,
font("Courier"),
px(16.0),
None,
1,
1,
FoldPlaceholder::default(),
DiagnosticSeverity::Warning,
cx,
)
});
cx.update(|cx| {
map.update(cx, |map, cx| {
map.highlight_text(
HighlightKey::Editor,
highlight_ranges,
HighlightStyle {
color: Some(Hsla::blue()),
..Default::default()
},
false,
cx,
);
});
});
let snapshot = cx.update(|cx| map.update(cx, |map, cx| map.snapshot(cx)));
let mut group = c.benchmark_group("Create highlight endpoints");
group.sample_size(10);
group.measurement_time(Duration::from_secs(10));
group.bench_with_input(
BenchmarkId::new("text_highlights", LINE_VIEW_PORT_COUNT),
&snapshot,
|bench, snapshot| {
bench.iter(|| {
black_box(snapshot.chunks(
DisplayRow(400)..DisplayRow(400 + LINE_VIEW_PORT_COUNT as u32),
language::LanguageAwareStyling {
tree_sitter: false,
diagnostics: false,
},
Default::default(),
));
});
},
);
group.finish();
}
criterion_group!(
benches,
to_tab_point_benchmark,
to_fold_point_benchmark,
create_highlight_endpoints_benchmark
);
criterion_main!(benches);

View file

@ -1,7 +1,7 @@
use collections::BTreeMap;
use gpui::HighlightStyle;
use language::{Chunk, LanguageAwareStyling};
use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot, ToOffset as _};
use multi_buffer::{MultiBufferChunks, MultiBufferOffset, MultiBufferSnapshot};
use std::{
cmp,
iter::{self, Peekable},
@ -81,6 +81,8 @@ fn create_highlight_endpoints(
if let Some(text_highlights) = text_highlights {
let start = buffer.anchor_after(range.start);
let end = buffer.anchor_after(range.end);
let mut v = Vec::new();
for (&tag, text_highlights) in text_highlights.iter() {
let style = text_highlights.0;
let ranges = &text_highlights.1;
@ -94,25 +96,39 @@ fn create_highlight_endpoints(
})
.unwrap_or_else(|i| i);
highlight_endpoints.reserve(2 * end_ix);
let ranges_ = &ranges[start_ix..][..end_ix];
v.clear();
v.reserve(ranges_.len());
highlight_endpoints.reserve(2 * ranges_.len());
for range in &ranges[start_ix..][..end_ix] {
let start = range.start.to_offset(buffer);
let end = range.end.to_offset(buffer);
if start == end {
continue;
}
highlight_endpoints.push(HighlightEndpoint {
offset: start,
tag,
style: Some(style),
});
highlight_endpoints.push(HighlightEndpoint {
offset: end,
tag,
style: None,
});
}
let mut iter = ranges_.iter();
buffer.summaries_for_anchors_cb(
ranges_.iter().map(|r| &r.start),
|start: MultiBufferOffset| {
v.push((start, iter.next().unwrap().end));
},
);
v.sort_by(|a, b| a.1.cmp(&b.1, buffer));
let mut iter = v.iter();
buffer.summaries_for_anchors_cb(
v.iter().map(|(_, end)| end),
|end: MultiBufferOffset| {
let start = iter.next().unwrap().0;
if start == end {
return;
}
highlight_endpoints.push(HighlightEndpoint {
offset: start,
tag,
style: Some(style),
});
highlight_endpoints.push(HighlightEndpoint {
offset: end,
tag,
style: None,
});
},
);
}
}
if let Some(semantic_token_highlights) = semantic_token_highlights {
@ -133,27 +149,50 @@ fn create_highlight_endpoints(
.then(cmp::Ordering::Less)
})
.unwrap_or_else(|i| i);
for token in &semantic_token_highlights[start_ix..] {
if token.range.start.cmp(&end, buffer).is_ge() {
break;
}
let end_ix = semantic_token_highlights[start_ix..]
.binary_search_by(|probe| {
probe
.range
.start
.cmp(&end, buffer)
.then(cmp::Ordering::Greater)
})
.unwrap_or_else(|i| i);
let start = token.range.start.to_offset(buffer);
let end = token.range.end.to_offset(buffer);
if start == end {
continue;
}
highlight_endpoints.push(HighlightEndpoint {
offset: start,
tag: HighlightKey::SemanticToken,
style: Some(interner[token.style]),
});
highlight_endpoints.push(HighlightEndpoint {
offset: end,
tag: HighlightKey::SemanticToken,
style: None,
});
}
let ranges_ = &semantic_token_highlights[start_ix..][..end_ix];
let mut ranges_with_offsets = Vec::with_capacity(ranges_.len());
highlight_endpoints.reserve(2 * ranges_.len());
let mut iter = ranges_.iter();
buffer.summaries_for_anchors_cb(
ranges_.iter().map(|token| &token.range.start),
|start: MultiBufferOffset| {
ranges_with_offsets.push((start, iter.next().unwrap()));
},
);
ranges_with_offsets.sort_by(|a, b| a.1.range.end.cmp(&b.1.range.end, buffer));
let mut iter = ranges_with_offsets.iter();
buffer.summaries_for_anchors_cb(
ranges_with_offsets
.iter()
.map(|(_, token)| &token.range.end),
|end: MultiBufferOffset| {
let (start, token) = iter.next().unwrap();
if *start == end {
return;
}
highlight_endpoints.push(HighlightEndpoint {
offset: *start,
tag: HighlightKey::SemanticToken,
style: Some(interner[token.style]),
});
highlight_endpoints.push(HighlightEndpoint {
offset: end,
tag: HighlightKey::SemanticToken,
style: None,
});
},
);
}
}
highlight_endpoints.sort();

View file

@ -5003,6 +5003,20 @@ impl MultiBufferSnapshot {
}
pub fn summaries_for_anchors<'a, MBD, I>(&'a self, anchors: I) -> Vec<MBD>
where
MBD: MultiBufferDimension
+ Ord
+ Sub<Output = MBD::TextDimension>
+ AddAssign<MBD::TextDimension>,
MBD::TextDimension: Sub<Output = MBD::TextDimension> + Ord,
I: 'a + IntoIterator<Item = &'a Anchor>,
{
let mut summaries = Vec::new();
self.summaries_for_anchors_cb(anchors, |summary| summaries.push(summary));
summaries
}
pub fn summaries_for_anchors_cb<'a, MBD, I>(&'a self, anchors: I, mut cb: impl FnMut(MBD))
where
MBD: MultiBufferDimension
+ Ord
@ -5018,18 +5032,17 @@ impl MultiBufferSnapshot {
.cursor::<Dimensions<ExcerptDimension<MBD>, OutputDimension<MBD>>>(());
diff_transforms_cursor.next();
let mut summaries = Vec::new();
while let Some(anchor) = anchors.peek() {
let target = anchor.seek_target(self);
let excerpt_anchor = match anchor {
Anchor::Min => {
summaries.push(MBD::default());
cb(MBD::default());
anchors.next();
continue;
}
Anchor::Excerpt(excerpt_anchor) => excerpt_anchor,
Anchor::Max => {
summaries.push(MBD::from_summary(&self.text_summary()));
cb(MBD::from_summary(&self.text_summary()));
anchors.next();
continue;
}
@ -5047,7 +5060,7 @@ impl MultiBufferSnapshot {
excerpt_start_position,
&mut diff_transforms_cursor,
);
summaries.push(position);
cb(position);
anchors.next();
continue;
}
@ -5083,7 +5096,7 @@ impl MultiBufferSnapshot {
diff_transforms_cursor.seek_forward(&position, Bias::Left);
}
summaries.push(self.summary_for_anchor_with_excerpt_position(
cb(self.summary_for_anchor_with_excerpt_position(
excerpt_anchor,
position,
&mut diff_transforms_cursor,
@ -5097,12 +5110,10 @@ impl MultiBufferSnapshot {
excerpt_start_position,
&mut diff_transforms_cursor,
);
summaries.push(position);
cb(position);
anchors.next();
}
}
summaries
}
pub fn dimensions_from_points<'a, MBD>(