Summary
The name tagger's Aho-Corasick automaton is rebuilt from its full needle set on the first analyze_names(..., symbols=true) call of every process. That build is ~1.5s and is the dominant cost of first use / cold start. It is cached for the process lifetime afterward (the Tier 1 LazyLock<AhoCorasick>), so it is purely a startup cost.
plans/arch-rust-core.md already calls this out and names the fix:
aho-corasick v1 exposes no serde or raw-byte serialisation, so the build is from-source. If startup cost ever matters, daachorse (double-array AC with explicit serialisation) is the escape hatch.
This issue is that escape hatch, with measurements showing startup cost does matter (it dominates cold start in serverless / short-lived-process deployments) and that the win is large.
Prototype and numbers
Swapping aho-corasick for daachorse (double-array Aho-Corasick, supports zero-copy de/serialization) on the person tagger's needle set, release build:
| step |
time |
| aho-corasick build (current) |
1553 ms |
| daachorse build (one-time, build side) |
658 ms |
| daachorse serialize |
55 ms, 25.7 MB |
| daachorse deserialize (runtime) |
~7 ms |
Overlapping-search results are byte-identical to the current aho-corasick output across a set of probe haystacks. Net: the ~1.5s runtime build becomes a ~7ms load (plus faulting/decompressing the embedded artifact), with output unchanged.
A note on size: the deduped needle count actually fed to the automaton measured ~470k (470,083 unique forms from 564,529 aliases). arch-rust-core.md estimates Tier 1 at ~150k patterns, which appears to count corpus lines/groups rather than the expanded, deduped per-form needle set.
On the rejected-formats list
arch-rust-core.md rejects bincode / postcard / rkyv with "zero-copy gains are irrelevant after init." That reasoning targets the small Tier-2/3 data and the steady state. The daachorse gain here is different: it removes the ~1.5s automaton build, which is paid at init. That is the "if startup cost ever matters" case the same doc explicitly carves out for daachorse, not the post-init zero-copy case that was rejected.
Scope and open questions (would want maintainer input before a PR)
- Engine swap:
aho-corasick -> daachorse in names::matcher::Needles, or a serialized fast path alongside it. daachorse is case-sensitive; rigour already pre-normalizes (CASEFOLD) both needles and haystack, so the current ascii_case_insensitive(true) is belt-and-suspenders, but full-suite parity needs verifying.
- Where to serialize:
build.rs cannot call the crate's own builder, so it would need a small gen step / workspace member (or genscripts/), or committing the serialized artifact.
- Flag-keyed cache: precompute only for the default
Normalize flags; other flag combinations fall back to a runtime build.
- Artifact size: ~25.7 MB raw (zstd-compressible like the existing corpus), embedded in the wheel.
Happy to implement this as a PR if the approach sounds reasonable. Prototype bench is available.
Summary
The name tagger's Aho-Corasick automaton is rebuilt from its full needle set on the first
analyze_names(..., symbols=true)call of every process. That build is ~1.5s and is the dominant cost of first use / cold start. It is cached for the process lifetime afterward (the Tier 1LazyLock<AhoCorasick>), so it is purely a startup cost.plans/arch-rust-core.mdalready calls this out and names the fix:This issue is that escape hatch, with measurements showing startup cost does matter (it dominates cold start in serverless / short-lived-process deployments) and that the win is large.
Prototype and numbers
Swapping
aho-corasickfordaachorse(double-array Aho-Corasick, supports zero-copy de/serialization) on the person tagger's needle set, release build:Overlapping-search results are byte-identical to the current
aho-corasickoutput across a set of probe haystacks. Net: the ~1.5s runtime build becomes a ~7ms load (plus faulting/decompressing the embedded artifact), with output unchanged.A note on size: the deduped needle count actually fed to the automaton measured ~470k (470,083 unique forms from 564,529 aliases).
arch-rust-core.mdestimates Tier 1 at ~150k patterns, which appears to count corpus lines/groups rather than the expanded, deduped per-form needle set.On the rejected-formats list
arch-rust-core.mdrejects bincode / postcard / rkyv with "zero-copy gains are irrelevant after init." That reasoning targets the small Tier-2/3 data and the steady state. The daachorse gain here is different: it removes the ~1.5s automaton build, which is paid at init. That is the "if startup cost ever matters" case the same doc explicitly carves out for daachorse, not the post-init zero-copy case that was rejected.Scope and open questions (would want maintainer input before a PR)
aho-corasick->daachorseinnames::matcher::Needles, or a serialized fast path alongside it. daachorse is case-sensitive; rigour already pre-normalizes (CASEFOLD) both needles and haystack, so the currentascii_case_insensitive(true)is belt-and-suspenders, but full-suite parity needs verifying.build.rscannot call the crate's own builder, so it would need a small gen step / workspace member (orgenscripts/), or committing the serialized artifact.Normalizeflags; other flag combinations fall back to a runtime build.Happy to implement this as a PR if the approach sounds reasonable. Prototype bench is available.