Files
keydr/benches/ngram_benchmarks.rs

145 lines
4.6 KiB
Rust

use criterion::{Criterion, black_box, criterion_group, criterion_main};
use keydr::engine::key_stats::KeyStatsStore;
use keydr::engine::ngram_stats::{
BigramKey, BigramStatsStore, TrigramStatsStore, extract_ngram_events,
};
use keydr::session::result::KeyTime;
fn make_keystrokes(count: usize) -> Vec<KeyTime> {
let chars = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
(0..count)
.map(|i| KeyTime {
key: chars[i % chars.len()],
time_ms: 200.0 + (i % 50) as f64,
correct: i % 7 != 0, // ~14% error rate
})
.collect()
}
fn bench_extraction(c: &mut Criterion) {
let keystrokes = make_keystrokes(500);
c.bench_function("extract_ngram_events (500 keystrokes)", |b| {
b.iter(|| {
extract_ngram_events(black_box(&keystrokes), 800.0)
})
});
}
fn bench_update(c: &mut Criterion) {
let keystrokes = make_keystrokes(500);
let (bigram_events, _) = extract_ngram_events(&keystrokes, 800.0);
c.bench_function("bigram_stats update (400 events)", |b| {
b.iter(|| {
let mut store = BigramStatsStore::default();
for ev in bigram_events.iter().take(400) {
store.update(
black_box(ev.key.clone()),
black_box(ev.total_time_ms),
black_box(ev.correct),
black_box(ev.has_hesitation),
0,
);
}
store
})
});
}
fn bench_focus_selection(c: &mut Criterion) {
// Use a-z + A-Z + 0-9 = 62 chars for up to 3844 unique bigrams
let all_chars: Vec<char> = ('a'..='z').chain('A'..='Z').chain('0'..='9').collect();
let mut bigram_stats = BigramStatsStore::default();
let mut char_stats = KeyStatsStore::default();
for &ch in &all_chars {
let stat = char_stats.stats.entry(ch).or_default();
stat.confidence = 0.8;
stat.filtered_time_ms = 430.0;
stat.sample_count = 50;
stat.total_count = 50;
stat.error_count = 3;
}
let mut count: usize = 0;
for &a in &all_chars {
for &b in &all_chars {
if bigram_stats.stats.len() >= 3000 {
break;
}
let key = BigramKey([a, b]);
let stat = bigram_stats.stats.entry(key).or_default();
stat.confidence = 0.5 + (count % 50) as f64 * 0.01;
stat.sample_count = 25 + count % 30;
stat.error_count = 5 + count % 10;
stat.redundancy_streak = if count % 3 == 0 { 3 } else { 1 };
count += 1;
}
}
assert_eq!(bigram_stats.stats.len(), 3000);
let unlocked: Vec<char> = all_chars;
c.bench_function("weakest_bigram (3K entries)", |b| {
b.iter(|| {
bigram_stats.weakest_bigram(black_box(&char_stats), black_box(&unlocked))
})
});
}
fn bench_history_replay(c: &mut Criterion) {
// Build 500 drills of ~300 keystrokes each
let drills: Vec<Vec<KeyTime>> = (0..500)
.map(|_| make_keystrokes(300))
.collect();
c.bench_function("history replay (500 drills x 300 keystrokes)", |b| {
b.iter(|| {
let mut bigram_stats = BigramStatsStore::default();
let mut trigram_stats = TrigramStatsStore::default();
let mut key_stats = KeyStatsStore::default();
for (drill_idx, keystrokes) in drills.iter().enumerate() {
let (bigram_events, trigram_events) =
extract_ngram_events(keystrokes, 800.0);
for kt in keystrokes {
if kt.correct {
let stat = key_stats.stats.entry(kt.key).or_default();
stat.total_count += 1;
} else {
key_stats.update_key_error(kt.key);
}
}
for ev in &bigram_events {
bigram_stats.update(
ev.key.clone(), ev.total_time_ms, ev.correct, ev.has_hesitation,
drill_idx as u32,
);
}
for ev in &trigram_events {
trigram_stats.update(
ev.key.clone(), ev.total_time_ms, ev.correct, ev.has_hesitation,
drill_idx as u32,
);
}
}
(bigram_stats, trigram_stats, key_stats)
})
});
}
criterion_group!(
benches,
bench_extraction,
bench_update,
bench_focus_selection,
bench_history_replay,
);
criterion_main!(benches);