Add buffer benchmarks (#403)
Some checks are pending
CI / check (ubuntu-latest) (push) Waiting to run
CI / check (windows-latest) (push) Waiting to run

Also includes:
* criterion update to v0.6
* Simplify range parameter for `GapBuffer::extract_raw`
* Allow deleting multiple units via `TextBuffer::delete`
* And some additional smaller improvements
This commit is contained in:
Leonard Hecker 2025-06-03 18:05:16 +02:00 committed by GitHub
parent f07d3c3eb4
commit a3d9aac8f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 251 additions and 44 deletions

143
Cargo.lock generated
View file

@ -29,6 +29,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bumpalo"
version = "3.17.0"
@ -41,6 +47,17 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -101,25 +118,22 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "criterion"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"itertools 0.13.0",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
@ -132,7 +146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
"itertools 0.10.5",
]
[[package]]
@ -172,8 +186,11 @@ version = "1.1.0"
dependencies = [
"criterion",
"libc",
"serde",
"serde_json",
"windows-sys",
"winresource",
"zstd",
]
[[package]]
@ -188,6 +205,18 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi",
]
[[package]]
name = "half"
version = "2.6.0"
@ -204,12 +233,6 @@ version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
[[package]]
name = "hermit-abi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08"
[[package]]
name = "indexmap"
version = "2.9.0"
@ -220,17 +243,6 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "is-terminal"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
@ -240,12 +252,31 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jobserver"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@ -295,6 +326,12 @@ version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plotters"
version = "0.3.7"
@ -341,6 +378,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rayon"
version = "1.10.0"
@ -452,6 +495,12 @@ dependencies = [
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.101"
@ -536,6 +585,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
@ -704,3 +762,40 @@ dependencies = [
"toml",
"version_check",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.15+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
dependencies = [
"cc",
"pkg-config",
]

View file

@ -54,4 +54,7 @@ features = [
]
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
criterion = { version = "0.6", features = ["html_reports"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
zstd = { version = "0.13", default-features = false }

View file

@ -0,0 +1,5 @@
# editing-traces
This directory contains Seph Gentle's ASCII-only `rustcode` editing traces from: https://github.com/josephg/editing-traces
The trace was provided under the [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) license.

Binary file not shown.

View file

@ -2,12 +2,109 @@
// Licensed under the MIT License.
use std::hint::black_box;
use std::io::Cursor;
use std::mem;
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use edit::helpers::*;
use edit::simd::MemsetSafe;
use edit::{hash, oklab, simd, unicode};
use edit::{arena, buffer, hash, oklab, simd, unicode};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct EditingTracePatch(pub usize, pub usize, pub String);
#[derive(Deserialize)]
pub struct EditingTraceTransaction {
pub patches: Vec<EditingTracePatch>,
}
#[derive(Deserialize)]
pub struct EditingTraceData {
#[serde(rename = "startContent")]
pub start_content: String,
#[serde(rename = "endContent")]
pub end_content: String,
pub txns: Vec<EditingTraceTransaction>,
}
fn bench_buffer(c: &mut Criterion) {
let data = include_bytes!("../assets/editing-traces/rustcode.json.zst");
let data = zstd::decode_all(Cursor::new(data)).unwrap();
let data: EditingTraceData = serde_json::from_slice(&data).unwrap();
let mut patches_with_coords = Vec::new();
{
let mut tb = buffer::TextBuffer::new(false).unwrap();
tb.set_crlf(false);
tb.write(data.start_content.as_bytes(), true);
for t in &data.txns {
for p in &t.patches {
tb.cursor_move_to_offset(p.0);
let beg = tb.cursor_logical_pos();
tb.delete(buffer::CursorMovement::Grapheme, p.1 as CoordType);
tb.write(p.2.as_bytes(), true);
patches_with_coords.push((beg, p.1 as CoordType, p.2.clone()));
}
}
let mut actual = String::new();
tb.save_as_string(&mut actual);
assert_eq!(actual, data.end_content);
}
let bench_gap_buffer = || {
let mut buf = buffer::GapBuffer::new(false).unwrap();
buf.replace(0..usize::MAX, data.start_content.as_bytes());
for t in &data.txns {
for p in &t.patches {
buf.replace(p.0..p.0 + p.1, p.2.as_bytes());
}
}
buf
};
let bench_text_buffer = || {
let mut tb = buffer::TextBuffer::new(false).unwrap();
tb.set_crlf(false);
tb.write(data.start_content.as_bytes(), true);
for p in &patches_with_coords {
tb.cursor_move_to_logical(p.0);
tb.delete(buffer::CursorMovement::Grapheme, p.1);
tb.write(p.2.as_bytes(), true);
}
tb
};
// Sanity check: If this fails, the implementation is incorrect.
{
let buf = bench_gap_buffer();
let mut actual = Vec::new();
buf.extract_raw(0..usize::MAX, &mut actual, 0);
assert_eq!(actual, data.end_content.as_bytes());
}
{
let mut tb = bench_text_buffer();
let mut actual = String::new();
tb.save_as_string(&mut actual);
assert_eq!(actual, data.end_content);
}
c.benchmark_group("buffer")
.bench_function(BenchmarkId::new("GapBuffer", "rustcode"), |b| {
b.iter(bench_gap_buffer);
})
.bench_function(BenchmarkId::new("TextBuffer", "rustcode"), |b| {
b.iter(bench_text_buffer);
});
}
fn bench_hash(c: &mut Criterion) {
c.benchmark_group("hash")
@ -104,6 +201,9 @@ fn bench_unicode(c: &mut Criterion) {
}
fn bench(c: &mut Criterion) {
arena::init(128 * MEBI).unwrap();
bench_buffer(c);
bench_hash(c);
bench_oklab(c);
bench_simd_memchr2(c);

View file

@ -245,17 +245,9 @@ impl GapBuffer {
self.text_length = 0;
}
pub fn extract_raw(
&self,
mut beg: usize,
mut end: usize,
out: &mut Vec<u8>,
mut out_off: usize,
) {
debug_assert!(beg <= end && end <= self.text_length);
end = end.min(self.text_length);
beg = beg.min(end);
pub fn extract_raw(&self, range: Range<usize>, out: &mut Vec<u8>, mut out_off: usize) {
let end = range.end.min(self.text_length);
let mut beg = range.start.min(end);
out_off = out_off.min(out.len());
if beg >= end {

View file

@ -34,7 +34,7 @@ use std::ops::Range;
use std::rc::Rc;
use std::str;
use gap_buffer::GapBuffer;
pub use gap_buffer::GapBuffer;
use crate::arena::{ArenaString, scratch_arena};
use crate::cell::SemiRefCell;
@ -314,6 +314,11 @@ impl TextBuffer {
self.newlines_are_crlf
}
/// Changes the newline type without normalizing the document.
pub fn set_crlf(&mut self, crlf: bool) {
self.newlines_are_crlf = crlf;
}
/// Changes the newline type used in the document.
///
/// NOTE: Cannot be undone.
@ -920,6 +925,11 @@ impl TextBuffer {
self.selection_generation
}
/// Moves the cursor by `offset` and updates the selection to contain it.
pub fn selection_update_offset(&mut self, offset: usize) {
self.set_cursor_for_selection(self.cursor_move_to_offset_internal(self.cursor, offset));
}
/// Moves the cursor to `visual_pos` and updates the selection to contain it.
pub fn selection_update_visual(&mut self, visual_pos: Point) {
self.set_cursor_for_selection(self.cursor_move_to_visual_internal(self.cursor, visual_pos));
@ -1935,7 +1945,9 @@ impl TextBuffer {
/// The selection is cleared after the call.
/// Deletes characters from the buffer based on a delta from the cursor.
pub fn delete(&mut self, granularity: CursorMovement, delta: CoordType) {
debug_assert!(delta == -1 || delta == 1);
if delta == 0 {
return;
}
let mut beg;
let mut end;
@ -1943,8 +1955,8 @@ impl TextBuffer {
if let Some(r) = self.selection_range_internal(false) {
(beg, end) = r;
} else {
if (delta == -1 && self.cursor.offset == 0)
|| (delta == 1 && self.cursor.offset >= self.text_length())
if (delta < 0 && self.cursor.offset == 0)
|| (delta > 0 && self.cursor.offset >= self.text_length())
{
// Nothing to delete.
return;
@ -2014,7 +2026,7 @@ impl TextBuffer {
let end = self.cursor_move_to_logical_internal(beg, Point { x: CoordType::MAX, y: end.y });
let mut replacement = Vec::new();
self.buffer.extract_raw(beg.offset, end.offset, &mut replacement, 0);
self.buffer.extract_raw(beg.offset..end.offset, &mut replacement, 0);
let initial_len = replacement.len();
let mut offset = 0;
@ -2078,7 +2090,7 @@ impl TextBuffer {
};
let mut out = Vec::new();
self.buffer.extract_raw(beg.offset, end.offset, &mut out, 0);
self.buffer.extract_raw(beg.offset..end.offset, &mut out, 0);
if delete && !out.is_empty() {
self.edit_begin(HistoryType::Delete, beg);
@ -2225,7 +2237,7 @@ impl TextBuffer {
// Copy the deleted portion into the undo entry.
let deleted = &mut undo.deleted;
self.buffer.extract_raw(off, to.offset, deleted, out_off);
self.buffer.extract_raw(off..to.offset, deleted, out_off);
// Delete the portion from the buffer by enlarging the gap.
let count = to.offset - off;