#![allow(clippy::incompatible_msrv)]
use std::hint::black_box;
use std::time::{Duration, Instant};
use unicode_normalization::UnicodeNormalization as Baseline;
use unicode_normalize_aligned::UnicodeNormalization as Aligned;
pub const GATE_RATIO: f64 = 0.90;
pub const FORMS: [&str; 4] = ["nfc", "nfd", "nfkc", "nfkd"];
pub fn corpora() -> Vec<(&'static str, String)> {
let ascii =
"The quick brown fox jumps over the lazy dog; 1234567890 times it went. ".repeat(220);
let latin1 =
"Élève déjà çà et là, garçon naïf: mère, père, fenêtre. Grüße aus Köln, schöne Bäume. "
.repeat(160);
let vietnamese = "Tiếng Việt là ngôn ngữ của người Việt và là quốc ngữ. ".repeat(240);
let vietnamese_nfd: String = Aligned::nfd(vietnamese.as_str()).map(|p| p.0).collect();
let korean = "한국어의 자모 분해 형태를 시험한다 다람쥐 헌 쳇바퀴에 타고파. ".repeat(220);
let jamo: String = Aligned::nfd(korean.as_str()).map(|p| p.0).collect();
let arabic = "\u{0628}\u{0650}\u{0633}\u{0652}\u{0645}\u{0650} \u{0627}\u{0644}\u{0644}\u{0651}\u{0647}\u{0650} \u{0627}\u{0644}\u{0631}\u{0651}\u{064E}\u{062D}\u{0652}\u{0645}\u{064E}\u{0646}\u{0650} \u{0627}\u{0644}\u{0631}\u{0651}\u{064E}\u{062D}\u{0650}\u{064A}\u{0645}\u{0650} "
.repeat(260);
let emoji = "\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466} \u{1F3F4}\u{200D}\u{2620}\u{FE0F} \u{1F468}\u{1F3FD}\u{200D}\u{1F692} \u{1F1FA}\u{1F1F8} "
.repeat(300);
vec![
("ascii", ascii),
("latin1", latin1),
("vietnamese-nfd", vietnamese_nfd),
("hangul-jamo", jamo),
("arabic-pointed", arabic),
("emoji-zwj", emoji),
]
}
pub fn ours(form: &str, text: &str) -> u64 {
let mut sum = 0u64;
match form {
"nfc" => {
for (c, t) in Aligned::nfc(text) {
sum = sum.wrapping_add(c as u64).wrapping_add(t as u64);
}
}
"nfd" => {
for (c, t) in Aligned::nfd(text) {
sum = sum.wrapping_add(c as u64).wrapping_add(t as u64);
}
}
"nfkc" => {
for (c, t) in Aligned::nfkc(text) {
sum = sum.wrapping_add(c as u64).wrapping_add(t as u64);
}
}
_ => {
for (c, t) in Aligned::nfkd(text) {
sum = sum.wrapping_add(c as u64).wrapping_add(t as u64);
}
}
}
sum
}
pub fn baseline(form: &str, text: &str) -> u64 {
let mut sum = 0u64;
match form {
"nfc" => {
for c in Baseline::nfc(text) {
sum = sum.wrapping_add(c as u64);
}
}
"nfd" => {
for c in Baseline::nfd(text) {
sum = sum.wrapping_add(c as u64);
}
}
"nfkc" => {
for c in Baseline::nfkc(text) {
sum = sum.wrapping_add(c as u64);
}
}
_ => {
for c in Baseline::nfkd(text) {
sum = sum.wrapping_add(c as u64);
}
}
}
sum
}
fn best_of(samples: u32, mut f: impl FnMut() -> u64) -> Duration {
let mut best = Duration::MAX;
for _ in 0..samples {
let start = Instant::now();
black_box(f());
best = best.min(start.elapsed());
}
best
}
pub fn gate() {
let mut worst: Option<(String, f64)> = None;
for (name, text) in corpora() {
for form in FORMS {
black_box(ours(form, &text));
black_box(baseline(form, &text));
let mut our_time = Duration::MAX;
let mut base_time = Duration::MAX;
for _ in 0..15 {
our_time = our_time.min(best_of(3, || ours(form, black_box(&text))));
base_time = base_time.min(best_of(3, || baseline(form, black_box(&text))));
}
let ratio = base_time.as_secs_f64() / our_time.as_secs_f64();
println!("gate {name}/{form}: {ratio:.3}x baseline");
assert!(
ratio >= GATE_RATIO,
"{name}/{form}: {ratio:.3}x is below the {GATE_RATIO}x floor"
);
if worst.as_ref().map_or(true, |w| ratio < w.1) {
worst = Some((format!("{name}/{form}"), ratio));
}
}
}
if let Some((name, ratio)) = worst {
println!("gate worst case {name}: {ratio:.3}x baseline");
}
}