зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-09-08 08:25:51 +03:00
chore(bench): restructure into focused targets, add runner, and tune slow cases (#10)
- Upgrade Criterion 0.5 → 0.8.2 - Split 4 broad bench files into 14 focused targets - Add Rust runner binary with per-target start/done logging - Add wrap_render benchmarks (all 4 WrapMode variants × 2 sizes) - Add undo_redo benchmarks (undo/redo × 3 history depths) - Apply benchmark_group + SamplingMode::Flat + reduced sample_size on slow insert cases to cut total run time from ~28 min to ~12 min - Rewrite bench/README.md to document runner, targets, and workflow
Этот коммит содержится в:
@@ -8,26 +8,70 @@ license = "MIT"
|
|||||||
[lib]
|
[lib]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "runner"
|
||||||
|
path = "src/bin/runner.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tui-textarea = { package = "tui-textarea-2", path = "..", features = ["no-backend", "search"] }
|
tui-textarea = { package = "tui-textarea-2", path = "..", features = ["no-backend", "search"] }
|
||||||
ratatui = { version = "0.30.0", default-features = false }
|
ratatui = { version = "0.30.0", default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.5"
|
criterion = "0.8.2"
|
||||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "insert"
|
name = "insert_append"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "search"
|
name = "insert_random"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "cursor"
|
name = "insert_long"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "delete"
|
name = "search_forward"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "search_backward"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "cursor_char"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "cursor_word"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "cursor_paragraph"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "cursor_edge"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "delete_char"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "delete_word"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "delete_line"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "wrap_render"
|
||||||
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "undo_redo"
|
||||||
harness = false
|
harness = false
|
||||||
|
|||||||
@@ -1,34 +1,74 @@
|
|||||||
Benchmarks for tui-textarea using [Criterion.rs][criterion].
|
Benchmarks for tui-textarea using [Criterion.rs][criterion].
|
||||||
|
|
||||||
To run all benchmarks:
|
## Running all benchmarks (Rust runner — recommended)
|
||||||
|
|
||||||
|
The Rust runner executes every benchmark target sequentially and prints
|
||||||
|
per-target start time, exit status, and wall duration.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo bench --benches
|
cargo run -p tui-textarea-bench --bin runner
|
||||||
```
|
```
|
||||||
|
|
||||||
To run specific benchmark suite:
|
Example output:
|
||||||
|
|
||||||
|
```
|
||||||
|
[1/12] START insert_append at 2026-02-18T12:00:00Z
|
||||||
|
[1/12] DONE insert_append status=ok duration=42.31s
|
||||||
|
[2/12] START insert_random at 2026-02-18T12:00:42Z
|
||||||
|
...
|
||||||
|
All 14 benchmark targets completed successfully.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running a single benchmark target
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo bench --bench insert
|
cargo bench -p tui-textarea-bench --bench insert_append
|
||||||
```
|
```
|
||||||
|
|
||||||
To filter benchmarks:
|
Available targets:
|
||||||
|
|
||||||
|
| Target | What it measures |
|
||||||
|
|--------------------|-------------------------------------------|
|
||||||
|
| `insert_append` | Appending lorem lines sequentially |
|
||||||
|
| `insert_random` | Inserting lorem lines at random positions |
|
||||||
|
| `insert_long` | Typing a long line without newlines |
|
||||||
|
| `search_forward` | Forward regex search |
|
||||||
|
| `search_backward` | Backward regex search |
|
||||||
|
| `cursor_char` | Character-level cursor movement |
|
||||||
|
| `cursor_word` | Word-level cursor movement |
|
||||||
|
| `cursor_paragraph` | Paragraph-level cursor movement |
|
||||||
|
| `cursor_edge` | Head/end and top/bottom jumps |
|
||||||
|
| `delete_char` | Deleting one character at a time |
|
||||||
|
| `delete_word` | Deleting one word at a time |
|
||||||
|
| `delete_line` | Deleting to line head |
|
||||||
|
| `wrap_render` | Rendering with Word/Glyph/WordOrGlyph wrap modes |
|
||||||
|
| `undo_redo` | Undo and redo traversal at varying history depths |
|
||||||
|
|
||||||
|
## Filtering within a target
|
||||||
|
|
||||||
|
Pass a filter string after `--` to run only matching cases:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo bench append::1_lorem
|
cargo bench -p tui-textarea-bench --bench insert_append -- append::10_lorem
|
||||||
```
|
```
|
||||||
|
|
||||||
To compare benchmark results with [critcmp][]:
|
## Comparing branches with [critcmp][]
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git checkout main
|
git checkout main
|
||||||
cargo bench -- --save-baseline base
|
cargo bench -p tui-textarea-bench -- --save-baseline base
|
||||||
|
|
||||||
git checkout your-feature
|
git checkout your-feature
|
||||||
cargo bench -- --save-baseline change
|
cargo bench -p tui-textarea-bench -- --save-baseline change
|
||||||
|
|
||||||
critcmp base change
|
critcmp base change
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Note on `cargo test -p tui-textarea-bench`
|
||||||
|
|
||||||
|
Running `cargo test` on this package is a smoke test only — it verifies
|
||||||
|
that the benchmark code compiles and runs without panicking, but produces
|
||||||
|
no timing data. Use `cargo bench` or the Rust runner for real measurements.
|
||||||
|
|
||||||
[criterion]: https://github.com/bheisler/criterion.rs
|
[criterion]: https://github.com/bheisler/criterion.rs
|
||||||
[critcmp]: https://github.com/BurntSushi/critcmp
|
[critcmp]: https://github.com/BurntSushi/critcmp
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
||||||
use tui_textarea::{CursorMove, TextArea};
|
|
||||||
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum Restore {
|
|
||||||
TopLeft,
|
|
||||||
BottomLeft,
|
|
||||||
BottomRight,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Restore {
|
|
||||||
fn cursor_move(self) -> Option<CursorMove> {
|
|
||||||
match self {
|
|
||||||
Self::TopLeft => Some(CursorMove::Jump(0, 0)),
|
|
||||||
Self::BottomLeft => Some(CursorMove::Jump(u16::MAX, 0)),
|
|
||||||
Self::BottomRight => Some(CursorMove::Jump(u16::MAX, u16::MAX)),
|
|
||||||
Self::None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepare_textarea() -> TextArea<'static> {
|
|
||||||
let mut lines = Vec::with_capacity(LOREM.len() * 2 + 1);
|
|
||||||
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
|
||||||
lines.push("".to_string());
|
|
||||||
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
|
||||||
TextArea::new(lines)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(
|
|
||||||
mut textarea: TextArea<'_>,
|
|
||||||
moves: &[CursorMove],
|
|
||||||
restore: Restore,
|
|
||||||
repeat: usize,
|
|
||||||
) -> (usize, usize) {
|
|
||||||
let mut term = dummy_terminal();
|
|
||||||
|
|
||||||
let mut prev = textarea.cursor();
|
|
||||||
for _ in 0..repeat {
|
|
||||||
for m in moves {
|
|
||||||
textarea.move_cursor(*m);
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
}
|
|
||||||
if let Some(m) = restore.cursor_move() {
|
|
||||||
if textarea.cursor() == prev {
|
|
||||||
textarea.move_cursor(m);
|
|
||||||
prev = textarea.cursor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.cursor()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_char(c: &mut Criterion) {
|
|
||||||
let textarea = prepare_textarea();
|
|
||||||
c.bench_function("cursor::char::forward", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::Forward],
|
|
||||||
Restore::TopLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::char::back", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::Back],
|
|
||||||
Restore::BottomRight,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::char::down", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::Down],
|
|
||||||
Restore::TopLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::char::up", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::Up],
|
|
||||||
Restore::BottomLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn move_word(c: &mut Criterion) {
|
|
||||||
let textarea = prepare_textarea();
|
|
||||||
c.bench_function("cursor::word::forward", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::WordForward],
|
|
||||||
Restore::TopLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::word::back", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::WordBack],
|
|
||||||
Restore::BottomRight,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn move_paragraph(c: &mut Criterion) {
|
|
||||||
let textarea = prepare_textarea();
|
|
||||||
c.bench_function("cursor::paragraph::down", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::ParagraphForward],
|
|
||||||
Restore::TopLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::paragraph::up", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::ParagraphBack],
|
|
||||||
Restore::BottomLeft,
|
|
||||||
1000,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn move_edge(c: &mut Criterion) {
|
|
||||||
let textarea = prepare_textarea();
|
|
||||||
c.bench_function("cursor::edge::head_end", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::End, CursorMove::Head],
|
|
||||||
Restore::None,
|
|
||||||
500,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
c.bench_function("cursor::edge::top_bottom", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
black_box(run(
|
|
||||||
textarea.clone(),
|
|
||||||
&[CursorMove::Bottom, CursorMove::Top],
|
|
||||||
Restore::None,
|
|
||||||
500,
|
|
||||||
))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(cursor, move_char, move_word, move_paragraph, move_edge);
|
|
||||||
criterion_main!(cursor);
|
|
||||||
50
bench/benches/cursor_char.rs
Обычный файл
50
bench/benches/cursor_char.rs
Обычный файл
@@ -0,0 +1,50 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::CursorMove;
|
||||||
|
use tui_textarea_bench::{prepare_cursor_textarea, run_cursor, Restore};
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let textarea = prepare_cursor_textarea();
|
||||||
|
c.bench_function("cursor::char::forward", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::Forward],
|
||||||
|
Restore::TopLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::char::back", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::Back],
|
||||||
|
Restore::BottomRight,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::char::down", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::Down],
|
||||||
|
Restore::TopLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::char::up", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::Up],
|
||||||
|
Restore::BottomLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(cursor_char, bench);
|
||||||
|
criterion_main!(cursor_char);
|
||||||
30
bench/benches/cursor_edge.rs
Обычный файл
30
bench/benches/cursor_edge.rs
Обычный файл
@@ -0,0 +1,30 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::CursorMove;
|
||||||
|
use tui_textarea_bench::{prepare_cursor_textarea, run_cursor, Restore};
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let textarea = prepare_cursor_textarea();
|
||||||
|
c.bench_function("cursor::edge::head_end", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::End, CursorMove::Head],
|
||||||
|
Restore::None,
|
||||||
|
500,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::edge::top_bottom", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::Bottom, CursorMove::Top],
|
||||||
|
Restore::None,
|
||||||
|
500,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(cursor_edge, bench);
|
||||||
|
criterion_main!(cursor_edge);
|
||||||
30
bench/benches/cursor_paragraph.rs
Обычный файл
30
bench/benches/cursor_paragraph.rs
Обычный файл
@@ -0,0 +1,30 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::CursorMove;
|
||||||
|
use tui_textarea_bench::{prepare_cursor_textarea, run_cursor, Restore};
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let textarea = prepare_cursor_textarea();
|
||||||
|
c.bench_function("cursor::paragraph::down", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::ParagraphForward],
|
||||||
|
Restore::TopLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::paragraph::up", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::ParagraphBack],
|
||||||
|
Restore::BottomLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(cursor_paragraph, bench);
|
||||||
|
criterion_main!(cursor_paragraph);
|
||||||
30
bench/benches/cursor_word.rs
Обычный файл
30
bench/benches/cursor_word.rs
Обычный файл
@@ -0,0 +1,30 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::CursorMove;
|
||||||
|
use tui_textarea_bench::{prepare_cursor_textarea, run_cursor, Restore};
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let textarea = prepare_cursor_textarea();
|
||||||
|
c.bench_function("cursor::word::forward", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::WordForward],
|
||||||
|
Restore::TopLeft,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
c.bench_function("cursor::word::back", |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
std::hint::black_box(run_cursor(
|
||||||
|
textarea.clone(),
|
||||||
|
&[CursorMove::WordBack],
|
||||||
|
Restore::BottomRight,
|
||||||
|
1000,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(cursor_word, bench);
|
||||||
|
criterion_main!(cursor_word);
|
||||||
@@ -2,25 +2,13 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
|||||||
use tui_textarea::{CursorMove, TextArea};
|
use tui_textarea::{CursorMove, TextArea};
|
||||||
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum Kind {
|
|
||||||
Char,
|
|
||||||
Word,
|
|
||||||
Line,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn run(textarea: &TextArea<'_>, kind: Kind) {
|
fn run(textarea: &TextArea<'_>) {
|
||||||
let mut term = dummy_terminal();
|
let mut term = dummy_terminal();
|
||||||
let mut t = textarea.clone();
|
let mut t = textarea.clone();
|
||||||
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
let modified = match kind {
|
if !t.delete_char() {
|
||||||
Kind::Char => t.delete_char(),
|
|
||||||
Kind::Word => t.delete_word(),
|
|
||||||
Kind::Line => t.delete_line_by_head(),
|
|
||||||
};
|
|
||||||
if !modified {
|
|
||||||
t = textarea.clone();
|
t = textarea.clone();
|
||||||
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
}
|
}
|
||||||
@@ -34,11 +22,8 @@ fn bench(c: &mut Criterion) {
|
|||||||
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
}
|
}
|
||||||
let textarea = TextArea::new(lines);
|
let textarea = TextArea::new(lines);
|
||||||
|
c.bench_function("delete::char", |b| b.iter(|| run(&textarea)));
|
||||||
c.bench_function("delete::char", |b| b.iter(|| run(&textarea, Kind::Char)));
|
|
||||||
c.bench_function("delete::word", |b| b.iter(|| run(&textarea, Kind::Word)));
|
|
||||||
c.bench_function("delete::line", |b| b.iter(|| run(&textarea, Kind::Line)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(delete, bench);
|
criterion_group!(delete_char, bench);
|
||||||
criterion_main!(delete);
|
criterion_main!(delete_char);
|
||||||
29
bench/benches/delete_line.rs
Обычный файл
29
bench/benches/delete_line.rs
Обычный файл
@@ -0,0 +1,29 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::{CursorMove, TextArea};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn run(textarea: &TextArea<'_>) {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
let mut t = textarea.clone();
|
||||||
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
|
for _ in 0..100 {
|
||||||
|
if !t.delete_line_by_head() {
|
||||||
|
t = textarea.clone();
|
||||||
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
|
}
|
||||||
|
term.draw_textarea(&t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut lines = vec![];
|
||||||
|
for _ in 0..10 {
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
}
|
||||||
|
let textarea = TextArea::new(lines);
|
||||||
|
c.bench_function("delete::line", |b| b.iter(|| run(&textarea)));
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(delete_line, bench);
|
||||||
|
criterion_main!(delete_line);
|
||||||
29
bench/benches/delete_word.rs
Обычный файл
29
bench/benches/delete_word.rs
Обычный файл
@@ -0,0 +1,29 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::{CursorMove, TextArea};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn run(textarea: &TextArea<'_>) {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
let mut t = textarea.clone();
|
||||||
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
|
for _ in 0..100 {
|
||||||
|
if !t.delete_word() {
|
||||||
|
t = textarea.clone();
|
||||||
|
t.move_cursor(CursorMove::Jump(u16::MAX, u16::MAX));
|
||||||
|
}
|
||||||
|
term.draw_textarea(&t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut lines = vec![];
|
||||||
|
for _ in 0..10 {
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
}
|
||||||
|
let textarea = TextArea::new(lines);
|
||||||
|
c.bench_function("delete::word", |b| b.iter(|| run(&textarea)));
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(delete_word, bench);
|
||||||
|
criterion_main!(delete_word);
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
||||||
use rand::rngs::SmallRng;
|
|
||||||
use rand::{Rng, SeedableRng};
|
|
||||||
use tui_textarea::{CursorMove, Input, Key, TextArea};
|
|
||||||
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM, SEED};
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn append_lorem(repeat: usize) -> usize {
|
|
||||||
let mut textarea = TextArea::default();
|
|
||||||
let mut term = dummy_terminal();
|
|
||||||
for _ in 0..repeat {
|
|
||||||
for line in LOREM {
|
|
||||||
for c in line.chars() {
|
|
||||||
textarea.input(Input {
|
|
||||||
key: Key::Char(c),
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
shift: false,
|
|
||||||
});
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
textarea.input(Input {
|
|
||||||
key: Key::Enter,
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
shift: false,
|
|
||||||
});
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
}
|
|
||||||
textarea.lines().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn random_lorem(repeat: usize) -> usize {
|
|
||||||
let mut rng = SmallRng::from_seed(SEED);
|
|
||||||
let mut textarea = TextArea::default();
|
|
||||||
let mut term = dummy_terminal();
|
|
||||||
|
|
||||||
for _ in 0..repeat {
|
|
||||||
for line in LOREM {
|
|
||||||
let row = rng.gen_range(0..textarea.lines().len() as u16);
|
|
||||||
textarea.move_cursor(CursorMove::Jump(row, 0));
|
|
||||||
textarea.move_cursor(CursorMove::End);
|
|
||||||
|
|
||||||
textarea.input(Input {
|
|
||||||
key: Key::Enter,
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
shift: false,
|
|
||||||
});
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
|
|
||||||
for c in line.chars() {
|
|
||||||
textarea.input(Input {
|
|
||||||
key: Key::Char(c),
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
shift: false,
|
|
||||||
});
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.lines().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn append_long_lorem(repeat: usize) -> usize {
|
|
||||||
let mut textarea = TextArea::default();
|
|
||||||
let mut term = dummy_terminal();
|
|
||||||
|
|
||||||
for _ in 0..repeat {
|
|
||||||
for line in LOREM {
|
|
||||||
for c in line.chars() {
|
|
||||||
textarea.input(Input {
|
|
||||||
key: Key::Char(c),
|
|
||||||
ctrl: false,
|
|
||||||
alt: false,
|
|
||||||
shift: false,
|
|
||||||
});
|
|
||||||
term.draw_textarea(&textarea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea.lines().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn append(c: &mut Criterion) {
|
|
||||||
c.bench_function("insert::append::1_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_lorem(1)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::append::10_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_lorem(10)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::append::50_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_lorem(50)))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random(c: &mut Criterion) {
|
|
||||||
c.bench_function("insert::random::1_lorem", |b| {
|
|
||||||
b.iter(|| black_box(random_lorem(1)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::random::10_lorem", |b| {
|
|
||||||
b.iter(|| black_box(random_lorem(10)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::random::50_lorem", |b| {
|
|
||||||
b.iter(|| black_box(random_lorem(50)))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inserting a long line is slower than multiple short lines into `TextArea`
|
|
||||||
fn long(c: &mut Criterion) {
|
|
||||||
c.bench_function("insert::long::1_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_long_lorem(1)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::long::5_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_long_lorem(5)))
|
|
||||||
});
|
|
||||||
c.bench_function("insert::long::10_lorem", |b| {
|
|
||||||
b.iter(|| black_box(append_long_lorem(10)))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(insert, append, random, long);
|
|
||||||
criterion_main!(insert);
|
|
||||||
57
bench/benches/insert_append.rs
Обычный файл
57
bench/benches/insert_append.rs
Обычный файл
@@ -0,0 +1,57 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion, SamplingMode};
|
||||||
|
use tui_textarea::{Input, Key, TextArea};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn append_lorem(repeat: usize) -> usize {
|
||||||
|
let mut textarea = TextArea::default();
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
for _ in 0..repeat {
|
||||||
|
for line in LOREM {
|
||||||
|
for c in line.chars() {
|
||||||
|
textarea.input(Input {
|
||||||
|
key: Key::Char(c),
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false,
|
||||||
|
});
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textarea.input(Input {
|
||||||
|
key: Key::Enter,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false,
|
||||||
|
});
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
textarea.lines().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("insert::append");
|
||||||
|
|
||||||
|
// ~16ms/iter — fits 100 samples in ~6s, keep defaults
|
||||||
|
group.bench_function("1_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_lorem(1)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~490ms/iter — 20 samples × 490ms ≈ 10s
|
||||||
|
group.sampling_mode(SamplingMode::Flat);
|
||||||
|
group.sample_size(20);
|
||||||
|
group.bench_function("10_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_lorem(10)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~5.9s/iter — 10 samples × 5.9s ≈ 59s (minimum viable)
|
||||||
|
group.sample_size(10);
|
||||||
|
group.bench_function("50_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_lorem(50)))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(insert_append, bench);
|
||||||
|
criterion_main!(insert_append);
|
||||||
53
bench/benches/insert_long.rs
Обычный файл
53
bench/benches/insert_long.rs
Обычный файл
@@ -0,0 +1,53 @@
|
|||||||
|
// Inserting a long line is slower than multiple short lines into TextArea
|
||||||
|
use criterion::{criterion_group, criterion_main, Criterion, SamplingMode};
|
||||||
|
use tui_textarea::{Input, Key, TextArea};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn append_long_lorem(repeat: usize) -> usize {
|
||||||
|
let mut textarea = TextArea::default();
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
|
||||||
|
for _ in 0..repeat {
|
||||||
|
for line in LOREM {
|
||||||
|
for c in line.chars() {
|
||||||
|
textarea.input(Input {
|
||||||
|
key: Key::Char(c),
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false,
|
||||||
|
});
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.lines().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("insert::long");
|
||||||
|
|
||||||
|
// ~25ms/iter — fits 100 samples in ~6s, keep defaults
|
||||||
|
group.bench_function("1_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_long_lorem(1)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~324ms/iter — 20 samples × 324ms ≈ 6s
|
||||||
|
group.sampling_mode(SamplingMode::Flat);
|
||||||
|
group.sample_size(20);
|
||||||
|
group.bench_function("5_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_long_lorem(5)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~1.1s/iter — 10 samples × 1.1s ≈ 11s
|
||||||
|
group.sample_size(10);
|
||||||
|
group.bench_function("10_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(append_long_lorem(10)))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(insert_long, bench);
|
||||||
|
criterion_main!(insert_long);
|
||||||
67
bench/benches/insert_random.rs
Обычный файл
67
bench/benches/insert_random.rs
Обычный файл
@@ -0,0 +1,67 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion, SamplingMode};
|
||||||
|
use rand::rngs::SmallRng;
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use tui_textarea::{CursorMove, Input, Key, TextArea};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM, SEED};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn random_lorem(repeat: usize) -> usize {
|
||||||
|
let mut rng = SmallRng::from_seed(SEED);
|
||||||
|
let mut textarea = TextArea::default();
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
|
||||||
|
for _ in 0..repeat {
|
||||||
|
for line in LOREM {
|
||||||
|
let row = rng.gen_range(0..textarea.lines().len() as u16);
|
||||||
|
textarea.move_cursor(CursorMove::Jump(row, 0));
|
||||||
|
textarea.move_cursor(CursorMove::End);
|
||||||
|
|
||||||
|
textarea.input(Input {
|
||||||
|
key: Key::Enter,
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false,
|
||||||
|
});
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
|
||||||
|
for c in line.chars() {
|
||||||
|
textarea.input(Input {
|
||||||
|
key: Key::Char(c),
|
||||||
|
ctrl: false,
|
||||||
|
alt: false,
|
||||||
|
shift: false,
|
||||||
|
});
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.lines().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("insert::random");
|
||||||
|
|
||||||
|
// ~23ms/iter — fits 100 samples in ~5s, keep defaults
|
||||||
|
group.bench_function("1_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(random_lorem(1)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~419ms/iter — 20 samples × 419ms ≈ 8s
|
||||||
|
group.sampling_mode(SamplingMode::Flat);
|
||||||
|
group.sample_size(20);
|
||||||
|
group.bench_function("10_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(random_lorem(10)))
|
||||||
|
});
|
||||||
|
|
||||||
|
// ~2.1s/iter — 10 samples × 2.1s ≈ 21s
|
||||||
|
group.sample_size(10);
|
||||||
|
group.bench_function("50_lorem", |b| {
|
||||||
|
b.iter(|| std::hint::black_box(random_lorem(50)))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(insert_random, bench);
|
||||||
|
criterion_main!(insert_random);
|
||||||
37
bench/benches/search_backward.rs
Обычный файл
37
bench/benches/search_backward.rs
Обычный файл
@@ -0,0 +1,37 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn run(pat: &str, mut textarea: TextArea<'_>) {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
textarea.set_search_pattern(pat).unwrap();
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
for _ in 0..100 {
|
||||||
|
textarea.search_back(false);
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
textarea.set_search_pattern(r"").unwrap();
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short(c: &mut Criterion) {
|
||||||
|
let textarea = TextArea::from(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
c.bench_function("search::back_short", |b| {
|
||||||
|
b.iter(|| run(r"\w*i\w*", textarea.clone()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn long(c: &mut Criterion) {
|
||||||
|
let mut lines = vec![];
|
||||||
|
for _ in 0..10 {
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
}
|
||||||
|
let textarea = TextArea::new(lines);
|
||||||
|
c.bench_function("search::back_long", |b| {
|
||||||
|
b.iter(|| run(r"[A-Z]\w*", textarea.clone()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(search_backward, short, long);
|
||||||
|
criterion_main!(search_backward);
|
||||||
@@ -3,16 +3,12 @@ use tui_textarea::TextArea;
|
|||||||
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn run(pat: &str, mut textarea: TextArea<'_>, forward: bool) {
|
fn run(pat: &str, mut textarea: TextArea<'_>) {
|
||||||
let mut term = dummy_terminal();
|
let mut term = dummy_terminal();
|
||||||
textarea.set_search_pattern(pat).unwrap();
|
textarea.set_search_pattern(pat).unwrap();
|
||||||
term.draw_textarea(&textarea);
|
term.draw_textarea(&textarea);
|
||||||
for _ in 0..100 {
|
for _ in 0..100 {
|
||||||
if forward {
|
|
||||||
textarea.search_forward(false);
|
textarea.search_forward(false);
|
||||||
} else {
|
|
||||||
textarea.search_back(false);
|
|
||||||
}
|
|
||||||
term.draw_textarea(&textarea);
|
term.draw_textarea(&textarea);
|
||||||
}
|
}
|
||||||
textarea.set_search_pattern(r"").unwrap();
|
textarea.set_search_pattern(r"").unwrap();
|
||||||
@@ -22,10 +18,7 @@ fn run(pat: &str, mut textarea: TextArea<'_>, forward: bool) {
|
|||||||
fn short(c: &mut Criterion) {
|
fn short(c: &mut Criterion) {
|
||||||
let textarea = TextArea::from(LOREM.iter().map(|s| s.to_string()));
|
let textarea = TextArea::from(LOREM.iter().map(|s| s.to_string()));
|
||||||
c.bench_function("search::forward_short", |b| {
|
c.bench_function("search::forward_short", |b| {
|
||||||
b.iter(|| run(r"\w*i\w*", textarea.clone(), true))
|
b.iter(|| run(r"\w*i\w*", textarea.clone()))
|
||||||
});
|
|
||||||
c.bench_function("search::back_short", |b| {
|
|
||||||
b.iter(|| run(r"\w*i\w*", textarea.clone(), false))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,12 +29,9 @@ fn long(c: &mut Criterion) {
|
|||||||
}
|
}
|
||||||
let textarea = TextArea::new(lines);
|
let textarea = TextArea::new(lines);
|
||||||
c.bench_function("search::forward_long", |b| {
|
c.bench_function("search::forward_long", |b| {
|
||||||
b.iter(|| run(r"[A-Z]\w*", textarea.clone(), true))
|
b.iter(|| run(r"[A-Z]\w*", textarea.clone()))
|
||||||
});
|
|
||||||
c.bench_function("search::back_long", |b| {
|
|
||||||
b.iter(|| run(r"[A-Z]\w*", textarea.clone(), false))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(search, short, long);
|
criterion_group!(search_forward, short, long);
|
||||||
criterion_main!(search);
|
criterion_main!(search_forward);
|
||||||
66
bench/benches/undo_redo.rs
Обычный файл
66
bench/benches/undo_redo.rs
Обычный файл
@@ -0,0 +1,66 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion, SamplingMode};
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt};
|
||||||
|
|
||||||
|
/// Build a textarea with `depth` undo-able edits in history using insert_str,
|
||||||
|
/// interspersed with delete_str to cover the cursor-clamping code path.
|
||||||
|
fn make_history(depth: usize) -> TextArea<'static> {
|
||||||
|
let mut ta = TextArea::default();
|
||||||
|
for i in 0..depth {
|
||||||
|
if i % 4 == 3 {
|
||||||
|
// Every 4th edit is a delete_str — the operation fixed in e6cc8ab.
|
||||||
|
ta.delete_str(3);
|
||||||
|
} else {
|
||||||
|
ta.insert_str("hello ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ta
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn undo_all(textarea: &TextArea<'_>) -> usize {
|
||||||
|
let mut t = textarea.clone();
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
let mut count = 0;
|
||||||
|
while t.undo() {
|
||||||
|
term.draw_textarea(&t);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn redo_all(textarea: &TextArea<'_>) -> usize {
|
||||||
|
// Start from fully-undone state so redo has work to do.
|
||||||
|
let mut t = textarea.clone();
|
||||||
|
while t.undo() {}
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
let mut count = 0;
|
||||||
|
while t.redo() {
|
||||||
|
term.draw_textarea(&t);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("undo_redo");
|
||||||
|
// undo::50 ran Linear with R²=0.19 (Criterion warned "enable flat sampling").
|
||||||
|
// All depths benefit from Flat since history-clone cost varies with depth.
|
||||||
|
group.sampling_mode(SamplingMode::Flat);
|
||||||
|
|
||||||
|
for depth in [50usize, 200, 500] {
|
||||||
|
let ta = make_history(depth);
|
||||||
|
group.bench_function(format!("undo::{}", depth), |b| {
|
||||||
|
b.iter(|| std::hint::black_box(undo_all(&ta)))
|
||||||
|
});
|
||||||
|
group.bench_function(format!("redo::{}", depth), |b| {
|
||||||
|
b.iter(|| std::hint::black_box(redo_all(&ta)))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(undo_redo, bench);
|
||||||
|
criterion_main!(undo_redo);
|
||||||
57
bench/benches/wrap_render.rs
Обычный файл
57
bench/benches/wrap_render.rs
Обычный файл
@@ -0,0 +1,57 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, Criterion};
|
||||||
|
use tui_textarea::{TextArea, WrapMode};
|
||||||
|
use tui_textarea_bench::{dummy_terminal, TerminalExt, LOREM};
|
||||||
|
|
||||||
|
const MODES: &[WrapMode] = &[
|
||||||
|
WrapMode::None,
|
||||||
|
WrapMode::Word,
|
||||||
|
WrapMode::Glyph,
|
||||||
|
WrapMode::WordOrGlyph,
|
||||||
|
];
|
||||||
|
|
||||||
|
fn make_textarea(repeat: usize, mode: WrapMode) -> TextArea<'static> {
|
||||||
|
let mut lines = Vec::with_capacity(LOREM.len() * repeat);
|
||||||
|
for _ in 0..repeat {
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
}
|
||||||
|
let mut ta = TextArea::new(lines);
|
||||||
|
ta.set_wrap_mode(mode);
|
||||||
|
ta
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short text: 7 lines, each ~63 chars. DummyBackend width=40 → all lines wrap.
|
||||||
|
fn short(c: &mut Criterion) {
|
||||||
|
for mode in MODES {
|
||||||
|
let ta = make_textarea(1, *mode);
|
||||||
|
let name = format!("wrap_render::short::{:?}", mode);
|
||||||
|
c.bench_function(&name, |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
for _ in 0..50 {
|
||||||
|
term.draw_textarea(&ta);
|
||||||
|
}
|
||||||
|
std::hint::black_box(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long text: 70 lines, same wrapping pressure.
|
||||||
|
fn long(c: &mut Criterion) {
|
||||||
|
for mode in MODES {
|
||||||
|
let ta = make_textarea(10, *mode);
|
||||||
|
let name = format!("wrap_render::long::{:?}", mode);
|
||||||
|
c.bench_function(&name, |b| {
|
||||||
|
b.iter(|| {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
for _ in 0..50 {
|
||||||
|
term.draw_textarea(&ta);
|
||||||
|
}
|
||||||
|
std::hint::black_box(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(wrap_render, short, long);
|
||||||
|
criterion_main!(wrap_render);
|
||||||
124
bench/src/bin/runner.rs
Обычный файл
124
bench/src/bin/runner.rs
Обычный файл
@@ -0,0 +1,124 @@
|
|||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::{Instant, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
const TARGETS: &[&str] = &[
|
||||||
|
"insert_append",
|
||||||
|
"insert_random",
|
||||||
|
"insert_long",
|
||||||
|
"search_forward",
|
||||||
|
"search_backward",
|
||||||
|
"cursor_char",
|
||||||
|
"cursor_word",
|
||||||
|
"cursor_paragraph",
|
||||||
|
"cursor_edge",
|
||||||
|
"delete_char",
|
||||||
|
"delete_word",
|
||||||
|
"delete_line",
|
||||||
|
"wrap_render",
|
||||||
|
"undo_redo",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let total = TARGETS.len();
|
||||||
|
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
|
||||||
|
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||||
|
let workspace_root = Path::new(manifest_dir)
|
||||||
|
.parent()
|
||||||
|
.expect("bench package must have a parent workspace directory");
|
||||||
|
|
||||||
|
let mut failed: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
|
for (i, target) in TARGETS.iter().enumerate() {
|
||||||
|
let n = i + 1;
|
||||||
|
let timestamp = utc_now();
|
||||||
|
println!("[{}/{}] START {} at {}", n, total, target, timestamp);
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
|
||||||
|
let wall_start = Instant::now();
|
||||||
|
let result = Command::new(&cargo)
|
||||||
|
.args([
|
||||||
|
"bench",
|
||||||
|
"-p",
|
||||||
|
"tui-textarea-bench",
|
||||||
|
"--bench",
|
||||||
|
target,
|
||||||
|
"--",
|
||||||
|
"--verbose",
|
||||||
|
"--noplot",
|
||||||
|
])
|
||||||
|
.current_dir(workspace_root)
|
||||||
|
.status();
|
||||||
|
let duration = wall_start.elapsed();
|
||||||
|
|
||||||
|
let (status_str, ok) = match result {
|
||||||
|
Ok(s) if s.success() => ("ok", true),
|
||||||
|
Ok(_) => ("err", false),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(" error: failed to spawn cargo for {}: {}", target, e);
|
||||||
|
("err", false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[{}/{}] DONE {} status={} duration={:.2}s",
|
||||||
|
n,
|
||||||
|
total,
|
||||||
|
target,
|
||||||
|
status_str,
|
||||||
|
duration.as_secs_f64()
|
||||||
|
);
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
failed.push(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
if failed.is_empty() {
|
||||||
|
println!("All {} benchmark targets completed successfully.", total);
|
||||||
|
} else {
|
||||||
|
eprintln!("Failed targets ({}/{}):", failed.len(), total);
|
||||||
|
for t in &failed {
|
||||||
|
eprintln!(" - {}", t);
|
||||||
|
}
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format the current UTC time as `YYYY-MM-DDTHH:MM:SSZ`.
|
||||||
|
/// Uses only `std::time` — no external date crate needed.
|
||||||
|
fn utc_now() -> String {
|
||||||
|
let ts = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs();
|
||||||
|
|
||||||
|
let secs = (ts % 60) as u32;
|
||||||
|
let mins = ((ts / 60) % 60) as u32;
|
||||||
|
let hours = ((ts / 3600) % 24) as u32;
|
||||||
|
let days = ts / 86400;
|
||||||
|
|
||||||
|
let (year, month, day) = days_since_epoch_to_ymd(days);
|
||||||
|
format!(
|
||||||
|
"{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
|
||||||
|
year, month, day, hours, mins, secs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert days since Unix epoch (1970-01-01) to (year, month, day).
|
||||||
|
/// Algorithm: http://howardhinnant.github.io/date_algorithms.html
|
||||||
|
fn days_since_epoch_to_ymd(days: u64) -> (u32, u32, u32) {
|
||||||
|
let z = days as i64 + 719_468;
|
||||||
|
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
|
||||||
|
let doe = (z - era * 146_097) as u64;
|
||||||
|
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||||
|
let y = yoe as i64 + era * 400;
|
||||||
|
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||||
|
let mp = (5 * doy + 2) / 153;
|
||||||
|
let d = doy - (153 * mp + 2) / 5 + 1;
|
||||||
|
let m = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||||
|
let y = if m <= 2 { y + 1 } else { y };
|
||||||
|
(y as u32, m as u32, d as u32)
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use ratatui::layout::{Position, Size};
|
|||||||
use ratatui::prelude::backend::ClearType;
|
use ratatui::prelude::backend::ClearType;
|
||||||
use ratatui::Terminal;
|
use ratatui::Terminal;
|
||||||
use std::io;
|
use std::io;
|
||||||
use tui_textarea::TextArea;
|
use tui_textarea::{CursorMove, TextArea};
|
||||||
|
|
||||||
pub const LOREM: &[&str] = &[
|
pub const LOREM: &[&str] = &[
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do",
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do",
|
||||||
@@ -138,3 +138,55 @@ impl TerminalExt for Terminal<DummyBackend> {
|
|||||||
self.draw(|f| f.render_widget(textarea, f.area())).unwrap();
|
self.draw(|f| f.render_widget(textarea, f.area())).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Cursor benchmark helpers ---
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Restore {
|
||||||
|
TopLeft,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Restore {
|
||||||
|
pub fn cursor_move(self) -> Option<CursorMove> {
|
||||||
|
match self {
|
||||||
|
Self::TopLeft => Some(CursorMove::Jump(0, 0)),
|
||||||
|
Self::BottomLeft => Some(CursorMove::Jump(u16::MAX, 0)),
|
||||||
|
Self::BottomRight => Some(CursorMove::Jump(u16::MAX, u16::MAX)),
|
||||||
|
Self::None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare_cursor_textarea() -> TextArea<'static> {
|
||||||
|
let mut lines = Vec::with_capacity(LOREM.len() * 2 + 1);
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
lines.push("".to_string());
|
||||||
|
lines.extend(LOREM.iter().map(|s| s.to_string()));
|
||||||
|
TextArea::new(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_cursor(
|
||||||
|
mut textarea: TextArea<'_>,
|
||||||
|
moves: &[CursorMove],
|
||||||
|
restore: Restore,
|
||||||
|
repeat: usize,
|
||||||
|
) -> (usize, usize) {
|
||||||
|
let mut term = dummy_terminal();
|
||||||
|
let mut prev = textarea.cursor();
|
||||||
|
for _ in 0..repeat {
|
||||||
|
for m in moves {
|
||||||
|
textarea.move_cursor(*m);
|
||||||
|
term.draw_textarea(&textarea);
|
||||||
|
}
|
||||||
|
if let Some(m) = restore.cursor_move() {
|
||||||
|
if textarea.cursor() == prev {
|
||||||
|
textarea.move_cursor(m);
|
||||||
|
prev = textarea.cursor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textarea.cursor()
|
||||||
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user