#![allow(missing_docs)]
use clankerdiff_core::{
ContentProjection, DiffDocument, DiffPresentation, PresentationOptions,
testing::DocumentBuilder,
};
use clankerdiff_ratatui::KeyCode;
use clankerdiff_ratatui::{
DiffPreviewOptions, DiffPreviewState, DiffReviewState, render_diff_preview,
};
use clankerdiff_syntax::SyntaxHighlighter;
use clankerdiff_theme::ReviewTheme;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use std::{hint::black_box, sync::Arc};
#[path = "../tests/support/mod.rs"]
mod support;
use support::{ReviewHarness, key};
fn large_document(rows: usize) -> Arc<DiffDocument> {
DocumentBuilder::new()
.generated("src/large.rs", rows)
.build()
}
fn many_file_document(files: usize, rows_per_file: usize) -> Arc<DiffDocument> {
DocumentBuilder::new()
.generated_files(files, rows_per_file)
.build()
}
const SCROLL_STEPS: u64 = 100;
const PAGE_STEPS: u64 = 10;
const FILE_SWITCHES: u64 = 50;
#[allow(clippy::format_collect)]
fn presentation_creation(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("presentation_creation");
group.sample_size(20);
let theme = clankerdiff_theme::ReviewTheme::default();
for rows in [1_000, 10_000, 100_000] {
let document = large_document(rows);
group.bench_with_input(BenchmarkId::from_parameter(rows), &rows, |bencher, _| {
bencher
.iter(|| DiffReviewState::with_theme(black_box(document.clone()), theme.clone()));
});
}
let old = (1..=100_000)
.map(|line| format!("let value_{line} = {line};\n"))
.collect::<String>();
let new = old.replacen("let value_50000 = 50000;", "let value_50000 = 0;", 1);
let document = DocumentBuilder::new()
.changed_with_hunk_window("src/full.rs", &old, &new, 49_997..=50_003)
.build();
let mut projection = ContentProjection::default();
projection.set_full_file(document.files[0].path.clone(), true);
group.bench_function("full-file-100000", |bencher| {
bencher.iter(|| {
DiffPresentation::with_projection(
black_box(document.clone()),
PresentationOptions::default(),
black_box(&projection),
)
});
});
group.finish();
}
fn rendering(criterion: &mut Criterion) {
let document = large_document(10_000);
let mut group = criterion.benchmark_group("visible_rendering_10k");
group.sample_size(20);
group.bench_function("cold_unified_80x24", |bencher| {
bencher.iter_batched(
|| ReviewHarness::new(document.clone(), 80, 24),
|mut harness| black_box(harness.draw()),
BatchSize::SmallInput,
);
});
let mut warm = ReviewHarness::new(document.clone(), 80, 24);
warm.draw();
group.bench_function("warm_unified_80x24", |bencher| {
bencher.iter(|| black_box(warm.draw()));
});
let mut split = ReviewHarness::new(document.clone(), 140, 40);
split.draw();
group.bench_function("warm_split_140x40", |bencher| {
bencher.iter(|| black_box(split.draw()));
});
let mut cached_navigation = ReviewHarness::new(document.clone(), 100, 24);
cached_navigation.draw();
cached_navigation.input(key(KeyCode::Enter));
cached_navigation.draw();
let mut down = true;
group.bench_function("navigate_and_draw_100x24", |bencher| {
bencher.iter(|| {
let code = if down { KeyCode::Down } else { KeyCode::Up };
down = !down;
black_box(cached_navigation.input_and_draw(key(code)))
});
});
group.finish();
navigation(criterion, &document);
}
fn navigation(criterion: &mut Criterion, document: &Arc<DiffDocument>) {
let mut group = criterion.benchmark_group("navigation_10k");
group.sample_size(20);
group.throughput(Throughput::Elements(SCROLL_STEPS));
group.bench_function("scroll_100_new_rows_100x24", |bencher| {
bencher.iter_batched(
|| patch_harness(document.clone(), 100, 24),
|mut harness| {
for _ in 0..SCROLL_STEPS {
black_box(harness.input_and_draw(key(KeyCode::Down)));
}
},
BatchSize::SmallInput,
);
});
group.throughput(Throughput::Elements(PAGE_STEPS));
group.bench_function("page_down_10_new_viewports_100x24", |bencher| {
bencher.iter_batched(
|| patch_harness(document.clone(), 100, 24),
|mut harness| {
for _ in 0..PAGE_STEPS {
black_box(harness.input_and_draw(key(KeyCode::PageDown)));
}
},
BatchSize::SmallInput,
);
});
let files = many_file_document(100, 100);
group.throughput(Throughput::Elements(FILE_SWITCHES));
group.bench_function("switch_50_files_100x24", |bencher| {
bencher.iter_batched(
|| {
let mut harness = ReviewHarness::new(files.clone(), 100, 24);
harness.draw();
harness
},
|mut harness| {
for _ in 0..FILE_SWITCHES {
black_box(harness.input_and_draw(key(KeyCode::Down)));
}
},
BatchSize::SmallInput,
);
});
group.throughput(Throughput::Elements(2));
group.bench_function("cycle_split_and_unified_140x40", |bencher| {
bencher.iter_batched(
|| patch_harness(document.clone(), 140, 40),
|mut harness| {
black_box(harness.input_and_draw(key(KeyCode::Char('v'))));
black_box(harness.input_and_draw(key(KeyCode::Char('v'))));
},
BatchSize::SmallInput,
);
});
group.finish();
}
fn patch_harness(document: Arc<DiffDocument>, width: u16, height: u16) -> ReviewHarness {
let mut harness = ReviewHarness::new(document, width, height);
harness.draw();
harness.input(key(KeyCode::Enter));
harness.draw();
harness
}
fn preview_ownership(criterion: &mut Criterion) {
let document = large_document(10_000);
let file = &document.files[0];
let theme = ReviewTheme::default();
let mut highlighter = SyntaxHighlighter::default();
let options = DiffPreviewOptions::default();
let mut group = criterion.benchmark_group("preview_10k");
group.bench_function("input_clone", |bencher| {
bencher.iter(|| black_box(file.clone()));
});
group.bench_function("unchanged_owned_render", |bencher| {
bencher.iter(|| {
black_box(render_diff_preview(
file.clone(),
80,
&theme,
&mut highlighter,
options,
))
});
});
let mut state = DiffPreviewState::new(file.clone());
state.render(80, &theme, &mut highlighter, options);
group.bench_function("unchanged_shared_render", |bencher| {
bencher.iter(|| black_box(state.render(80, &theme, &mut highlighter, options)));
});
group.finish();
}
criterion_group!(benches, presentation_creation, rendering, preview_ownership);
criterion_main!(benches);