6281c5830c
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
137 lines
3.8 KiB
Rust
137 lines
3.8 KiB
Rust
//! Scale, measured in release: 20 000 generated notes indexed in under 5 s and
|
|
//! a single `update_note` in under 5 ms.
|
|
//!
|
|
//! Run with `cargo test -p notescraft-index --release --test perf -- --ignored
|
|
//! --nocapture`; the numbers are printed.
|
|
|
|
use std::time::Instant;
|
|
|
|
use notescraft_index::Index;
|
|
|
|
/// Notes generated for the scale run.
|
|
const NOTES: usize = 20_000;
|
|
|
|
fn note_body(i: usize) -> String {
|
|
format!(
|
|
"---\ntags: [topic/{}, status/{}]\naliases: [Alias{i}]\n---\n# Heading {i}\n\n\
|
|
Paragraph about [[Note {}]] and [[topic/Note {}]] and [[#Heading {i}]] ^blk-{i}\n\n\
|
|
Body #inline/{i} with [a link](Note {}.md) and ![[assets/pic.png]].\n",
|
|
i % 7,
|
|
i % 3,
|
|
i,
|
|
(i + 1) % NOTES,
|
|
(i + 2) % NOTES,
|
|
)
|
|
}
|
|
|
|
fn path_of(i: usize) -> String {
|
|
format!("folder{}/Note {i}.md", i % 20)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "perf: run with --release --ignored"]
|
|
fn twenty_thousand_notes_index_in_under_five_seconds() {
|
|
let bodies: Vec<String> = (0..NOTES).map(note_body).collect();
|
|
let mut index = Index::new();
|
|
index.add_file("assets/pic.png").unwrap();
|
|
|
|
let start = Instant::now();
|
|
for (i, body) in bodies.iter().enumerate() {
|
|
index.update_note(&path_of(i), body).unwrap();
|
|
}
|
|
let elapsed = start.elapsed();
|
|
println!("indexed {NOTES} notes in {elapsed:?}");
|
|
|
|
assert_eq!(index.notes().len(), NOTES);
|
|
assert!(elapsed.as_secs_f64() < 5.0, "20 000 notes must index in under 5 s, took {elapsed:?}");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "perf: run with --release --ignored"]
|
|
fn one_update_is_under_five_milliseconds() {
|
|
let bodies: Vec<String> = (0..NOTES).map(note_body).collect();
|
|
let mut index = Index::new();
|
|
for (i, body) in bodies.iter().enumerate() {
|
|
index.update_note(&path_of(i), body).unwrap();
|
|
}
|
|
|
|
let mut worst = std::time::Duration::ZERO;
|
|
let mut checked = 0;
|
|
for (i, body) in bodies.iter().enumerate() {
|
|
let path = path_of(i);
|
|
let start = Instant::now();
|
|
index.update_note(&path, body).unwrap();
|
|
let took = start.elapsed();
|
|
worst = worst.max(took);
|
|
checked += 1;
|
|
if checked >= 2_000 {
|
|
break;
|
|
}
|
|
}
|
|
println!("worst single update over {checked} notes: {worst:?}");
|
|
assert!(worst.as_secs_f64() < 0.005, "a single update must stay under 5 ms, worst was {worst:?}");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "perf: run with --release --ignored"]
|
|
fn queries_over_twenty_thousand_notes_stay_usable() {
|
|
let mut index = Index::new();
|
|
index.add_file("assets/pic.png").unwrap();
|
|
for i in 0..NOTES {
|
|
index.update_note(&path_of(i), ¬e_body(i)).unwrap();
|
|
}
|
|
|
|
for (name, took) in [
|
|
(
|
|
"resolve",
|
|
query_time(&index, |i| {
|
|
let _ = i.resolve("folder0/Note 0.md", "Note 12345");
|
|
}),
|
|
),
|
|
(
|
|
"outgoing",
|
|
query_time(&index, |i| {
|
|
let _ = i.outgoing("folder0/Note 0.md");
|
|
}),
|
|
),
|
|
(
|
|
"unresolved",
|
|
query_time(&index, |i| {
|
|
let _ = i.unresolved();
|
|
}),
|
|
),
|
|
(
|
|
"backlinks",
|
|
query_time(&index, |i| {
|
|
let _ = i.backlinks("folder0/Note 0.md");
|
|
}),
|
|
),
|
|
(
|
|
"orphans",
|
|
query_time(&index, |i| {
|
|
let _ = i.orphans();
|
|
}),
|
|
),
|
|
(
|
|
"edges",
|
|
query_time(&index, |i| {
|
|
let _ = i.all_link_edges();
|
|
}),
|
|
),
|
|
(
|
|
"tags",
|
|
query_time(&index, |i| {
|
|
let _ = i.tags();
|
|
}),
|
|
),
|
|
] {
|
|
println!("{name}: {took:?}");
|
|
}
|
|
}
|
|
|
|
fn query_time(index: &Index, query: impl Fn(&Index)) -> std::time::Duration {
|
|
let start = Instant::now();
|
|
query(index);
|
|
start.elapsed()
|
|
}
|