edge-bench-rs: libdeflate for chunks, memchr for lines and tabs

libdeflate inflates a chunk whole, in half the time of zlib-rs (1.09 ->
0.51 s per boneless range); memchr finds line ends and tabs with SIMD.
The per-article timers are gone. Boneless 2026 ~700k headers/s, ath 2013
~920k; all three captures still identical to the TypeScript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daan MeijerandClaude Opus 5.5 committed 2026-10-09 09:34:38 +02:00
1 parent b1c2db5984
commit c7b4ebe470
4 files changed
+47 -25

No files matched your search

+20
View File
@@ -120,7 +120,9 @@ dependencies = [
"base64", "base64",
"chrono", "chrono",
"flate2", "flate2",
"libdeflater",
"md-5", "md-5",
"memchr",
"mimalloc", "mimalloc",
"regex", "regex",
"rustc-hash", "rustc-hash",
@@ -239,6 +241,24 @@ version = "0.2.190"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
[[package]]
name = "libdeflate-sys"
version = "1.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7870e5fbd2766179a937c725fb11f4ca0ef025d982beb61bd3ce755425bd19c"
dependencies = [
"cc",
]
[[package]]
name = "libdeflater"
version = "1.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cbb8285d2e2bd4b8d4155840ae81929354b4ba0d7b3c272c84761ffa2141e1f"
dependencies = [
"libdeflate-sys",
]
[[package]] [[package]]
name = "libmimalloc-sys" name = "libmimalloc-sys"
version = "0.1.49" version = "0.1.49"
+2
View File
@@ -8,7 +8,9 @@ description = "The edge worker's indexing (lib/edge RangeIndexer) in Rust, to co
base64 = "0.22" base64 = "0.22"
chrono = "0.4" chrono = "0.4"
flate2 = { version = "1", default-features = false, features = ["zlib-rs"] } flate2 = { version = "1", default-features = false, features = ["zlib-rs"] }
libdeflater = "1.26.1"
md-5 = "0.10" md-5 = "0.10"
memchr = "2.8.3"
mimalloc = "0.1.52" mimalloc = "0.1.52"
regex = "1" regex = "1"
rustc-hash = "2" rustc-hash = "2"
+7 -15
View File
@@ -195,9 +195,7 @@ impl Scanner {
let file = match self.files.get(self.key.as_str()) { let file = match self.files.get(self.key.as_str()) {
Some(f) => f.clone(), Some(f) => f.clone(),
None => { None => {
let t = std::time::Instant::now();
let f = describe(subject, name_end, &counter, from, xref, group).map(Rc::new); let f = describe(subject, name_end, &counter, from, xref, group).map(Rc::new);
unsafe { PROF[0] += t.elapsed().as_nanos() as u64; PROF[1] += 1; }
self.files.insert(self.key.clone(), f.clone()); self.files.insert(self.key.clone(), f.clone());
f f
} }
@@ -212,7 +210,7 @@ impl Scanner {
message_id: id.to_string(), message_id: id.to_string(),
bytes: js::parse_int(bytes).unwrap_or(0), bytes: js::parse_int(bytes).unwrap_or(0),
article, article,
posted_at: { let t = std::time::Instant::now(); let d = js::parse_date(date); unsafe { PROF[2] += t.elapsed().as_nanos() as u64; } d }, posted_at: js::parse_date(date),
file, file,
}) })
} }
@@ -244,20 +242,18 @@ fn scan_overview_text(text: &str, mut line: impl FnMut(Option<i64>, &str, &str,
let b = text.as_bytes(); let b = text.as_bytes();
let mut start = 0; let mut start = 0;
while start < b.len() { while start < b.len() {
let end = memchr(b'\n', &b[start..]).map(|i| i + start).unwrap_or(b.len()); let end = memchr::memchr(b'\n', &b[start..]).map(|i| i + start).unwrap_or(b.len());
let stop = if end > start && b[end - 1] == b'\r' { end - 1 } else { end }; let stop = if end > start && b[end - 1] == b'\r' { end - 1 } else { end };
if stop > start { if stop > start {
let l = &text[start..stop]; let l = &text[start..stop];
let lb = l.as_bytes(); let lb = l.as_bytes();
let mut tabs = [usize::MAX; 9]; let mut tabs = [usize::MAX; 9];
let mut n = 0; let mut n = 0;
for (i, &c) in lb.iter().enumerate() { for i in memchr::memchr_iter(b'\t', lb) {
if c == b'\t' { tabs[n] = i;
tabs[n] = i; n += 1;
n += 1; if n == 9 {
if n == 9 { break;
break;
}
} }
} }
if n >= 5 { if n >= 5 {
@@ -279,10 +275,6 @@ fn scan_overview_text(text: &str, mut line: impl FnMut(Option<i64>, &str, &str,
} }
} }
fn memchr(needle: u8, haystack: &[u8]) -> Option<usize> {
haystack.iter().position(|&c| c == needle)
}
struct OpenFile { struct OpenFile {
parts: Vec<Seg>, parts: Vec<Seg>,
seen: FxHashSet<u32>, seen: FxHashSet<u32>,
+18 -10
View File
@@ -15,21 +15,28 @@ use base64::Engine;
use indexer::{Batch, RangeIndexer}; use indexer::{Batch, RangeIndexer};
use js::json_str; use js::json_str;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::io::{Read, Write}; use std::io::Write;
use std::time::Instant; use std::time::Instant;
// Many small strings (message-ids, names): the system allocator was a measurable share. // Many small strings (message-ids, names): the system allocator was a measurable share.
#[global_allocator] #[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn inflate(raw: &[u8]) -> Vec<u8> { /// A chunk as the news server sent it (zlib; gzip also taken), whole: libdeflate wants the output
let mut out = Vec::with_capacity(raw.len() * 6); /// size, so it grows until the text fits (~3.6 times the compressed size on boneless).
if raw.starts_with(&[0x1f, 0x8b]) { fn inflate(raw: &[u8], d: &mut libdeflater::Decompressor) -> Vec<u8> {
flate2::read::MultiGzDecoder::new(raw).read_to_end(&mut out).unwrap(); let mut out = vec![0u8; raw.len() * 4 + 4096];
} else { loop {
flate2::read::ZlibDecoder::new(raw).read_to_end(&mut out).unwrap(); let result = if raw.starts_with(&[0x1f, 0x8b]) { d.gzip_decompress(raw, &mut out) } else { d.zlib_decompress(raw, &mut out) };
match result {
Ok(n) => {
out.truncate(n);
return out;
}
Err(libdeflater::DecompressionError::InsufficientSpace) => out.resize(out.len() * 2, 0),
Err(e) => panic!("inflate: {e}"),
}
} }
out
} }
fn json_field(json: &str, name: &str) -> String { fn json_field(json: &str, name: &str) -> String {
@@ -166,12 +173,13 @@ fn main() {
let (mut compressed, mut text_bytes) = (0usize, 0usize); let (mut compressed, mut text_bytes) = (0usize, 0usize);
let mut out = (0u64, 0usize, 0usize, 0usize, 0usize, 0usize, 0usize, 0usize); // headers, releases, files, segments, payload, candidates, fragments, samples let mut out = (0u64, 0usize, 0usize, 0usize, 0usize, 0usize, 0usize, 0usize); // headers, releases, files, segments, payload, candidates, fragments, samples
let mut keys = std::collections::HashSet::new(); let mut keys = std::collections::HashSet::new();
let mut decompressor = libdeflater::Decompressor::new();
let wall = Instant::now(); let wall = Instant::now();
for (i, file) in files.iter().enumerate() { for (i, file) in files.iter().enumerate() {
let raw = std::fs::read(format!("{dir}/{file}")).unwrap(); let raw = std::fs::read(format!("{dir}/{file}")).unwrap();
compressed += raw.len(); compressed += raw.len();
let t = Instant::now(); let t = Instant::now();
let data = inflate(&raw); let data = inflate(&raw, &mut decompressor);
inflate_s += t.elapsed().as_secs_f64(); inflate_s += t.elapsed().as_secs_f64();
text_bytes += data.len(); text_bytes += data.len();
let t = Instant::now(); let t = Instant::now();
@@ -206,7 +214,7 @@ fn main() {
let total = inflate_s + index_s; let total = inflate_s + index_s;
#[allow(static_mut_refs)] #[allow(static_mut_refs)]
let p = unsafe { indexer::PROF }; let p = unsafe { indexer::PROF };
eprintln!("describe {:.2} s ({} calls), dates {:.2} s, aggregate {:.2} s, evict+cutoff {:.2} s, judge+encode {:.2} s", p[0] as f64 / 1e9, p[1], p[2] as f64 / 1e9, p[3] as f64 / 1e9, p[4] as f64 / 1e9, p[5] as f64 / 1e9); eprintln!(" of which aggregate {:.2} s, evict+cutoff {:.2} s, judge+encode {:.2} s", p[3] as f64 / 1e9, p[4] as f64 / 1e9, p[5] as f64 / 1e9);
println!("{group} {start}–{end}: {} chunks, {} MB compressed, {} MB text", files.len(), compressed / 1_000_000, text_bytes / 1_000_000); println!("{group} {start}–{end}: {} chunks, {} MB compressed, {} MB text", files.len(), compressed / 1_000_000, text_bytes / 1_000_000);
println!( println!(
"{} headers in {:.2} s ({:.0}/s on one core; wall {:.2} s)", "{} headers in {:.2} s ({:.0}/s on one core; wall {:.2} s)",