Benchmarks

Every performance number ACOR publishes is here, with the command that produces it. Nothing is an estimate.

The evidence comes in two kinds:

  • Round trips are structural — counted at the storage seam, identical on miniredis and on a real server, and enforced by tests on every CI run.
  • Timings are hardware-bound. Absolute nanoseconds will not match yours. The reproducible quantity is the ratio between configurations.

Round trips per operation

Enforced by TestRTT* in pkg/acor/rtt_claims_test.go; a change fails CI.

OperationV1V2
Find()11
Find() with EnableCache, warmn/a0
Find() with a Preset enginen/a0
FindParallel() / FindIndexParallel(), 63 chunks11
FindMany(), 3 texts11
Add(), 5-character keyword532
Add(), 26-character keyword5072

Both schemas read in one round trip: V1 issues one SMEMBERS, V2 pipelines two HGETALL calls into one trip. V1’s cost is on writes, where it walks the trie node by node — so it grows with the length of the keyword being added, not with the dictionary.

Multi-scan reads cost the same as one Find(), and do not grow with chunk count or batch size: the automaton is loaded once per call and every chunk or text is scanned against that snapshot. Before v1.5.0 each chunk loaded its own, so a 63-chunk text issued 63 reads.

go test -run RTT ./pkg/acor

# The same counts against a real server, which is what makes them structural
ACOR_INTEGRATION_ADDR=localhost:6379 go test -run RTT ./pkg/acor

Timings

Apple M4, darwin/arm64, Go 1.26, Redis 8 on loopback, -benchtime=200x. This produces the tables below in about 20 seconds:

redis-server --port 6379 --save "" --daemonize yes
ACOR_INTEGRATION_ADDR=localhost:6379 \
  go test -bench RealServer -benchmem -benchtime=200x -run '^$' ./pkg/acor

make bench runs the full sweep including the miniredis benchmarks — several minutes, and its timings are not published (see Caveats).

Find, 1000 keywords

Configurationns/opB/opallocs/opvs V1
V1129,06239,5191,070baseline
V2, no cache224,738121,1422,060~1.7x slower
V2 + EnableCache, warm8,6317,89862~15x faster
PresetBalanced2,2042,0484~59x faster

Find, 100 keywords

Configurationns/opB/opallocs/opvs V1
V191,7248,136158baseline
V2, no cache79,88511,144219~1.1x faster
V2 + EnableCache, warm3,1202,85710~29x faster
PresetBalanced2,2142,0484~41x faster

At 100 keywords V1 and V2 are close enough that the winner changes between runs; only the 1000-keyword gap is stable.

Add

Configurationns/opvs V1
V14,956,892baseline
V2353,149~14x faster

Bulk load, AddMany

AddMany plans the whole batch in one pass and commits it in a single transaction, so it costs two round trips regardless of batch size. One sample on the setup above:

Keywordsns/opB/opallocs/op
100422,665199,8501,079
1,0002,958,0261,668,5688,816

Reproduce with ACOR_INTEGRATION_ADDR=localhost:6379 make bench-module.

Run-to-run variance

The ns/op columns are one run. Repeat runs on the same idle laptop moved every absolute number by 20-25% while the ratios held to within about 15% — which is why ratios are stated approximately and raw numbers are labelled a sample. Absolute figures differing from ours is expected; ratios differing substantially is worth an issue.

What the numbers mean

V2 without caching is somewhat slower than V1 on reads, because of payload rather than round trips: both cost one trip, but V1’s SMEMBERS returns just the keyword set while V2 must read an outputs hash carrying one entry per trie state. Roughly parity at 100 keywords, ~1.7x at 1000. Both schemas memoize the automaton, so an unchanged collection is not re-parsed between reads; uncached V2 was ~9x slower before that memoization, and what remains is inherent to reading the whole outputs hash.

The large read speedups come from caching, not from the schema. 15x to 59x belongs to EnableCache and the Preset engines. Choosing V2 alone does not deliver them.

V2’s unambiguous win is writes — ~14x on Add(), and it is the only schema supporting caching or preset engines at all. Use AddMany rather than a loop over Add: in the sample above, 1,000 keywords cost 3.0 ms instead of the ~350 ms the same writes cost one at a time.

Practical reading: choose V2, and enable EnableCache or a Preset if reads dominate. V2 with neither is the one configuration these numbers do not recommend.

Caveats

  • Loopback Redis has almost no network latency. Over a real network both schemas still pay one round trip on Find(), so V2’s larger payload matters more rather than less, and V1’s per-node Add() cost grows with the added latency.
  • These figures compare ACOR configurations against each other, not against other Aho-Corasick implementations. A single process with a static dictionary is better served by an in-memory library; ACOR earns its cost when several instances share one dictionary that changes at runtime.
  • Every other benchmark in the repository runs on miniredis, an in-process emulator with no round-trip cost. Those exist for regression detection and are deliberately not published here.