Performance
A throughput figure on its own says little: it depends on the text, on how many patterns there are and how often they hit, and on whether the implementation being compared did the same work. So these measurements follow the discipline of the Rust crate's rebar suite. The corpora are rebar's own, fetched from a pinned commit and checked against their SHA-256. Every implementation runs the same pattern set over the same bytes in the same search mode. And every row carries the number of matches found, which must agree between all the implementations on that row and, where rebar records a count for that pattern set and haystack, must reproduce it. A benchmark whose counts differ is measuring different work, and the report script fails on it.
| Column | What it is |
|---|---|
| OCaml | This library, through compat/ocaml/driver.exe. |
| Rust default |
The aho-corasick crate as a caller gets it: it
chooses between its NFAs and a DFA, and for small pattern
sets runs a SIMD prefilter (Teddy) that scans for candidate
bytes without stepping the automaton at all.
|
| Rust NFA | The same crate forced to its non-contiguous NFA with the prefilter off: the automaton alone, and the closest structural match to this library. |
| ahocorasick_rs | The Rust crate behind a Python binding, called from Python. |
| pyahocorasick | An independent C implementation, called from Python. |
The search modes are the ones on the
compatibility page:
overlapping is find_all and
find_overlapping_iter; overlapping_iter
is find_iter, consumed lazily; standard is
Stream.feed_nonoverlapping over the whole input and
Rust's default find_iter; leftmost_longest
and replace are the leftmost-longest search and
replace_all; and the _stream modes feed the
input in 4 KiB chunks. The Python columns time the call from
Python, Python objects per match included, which is what a Python
caller pays. Each cell is the median of at least three runs and at
least one second of running, on one core, after building the
automaton once.
Results
Generated by bench/report.py from one run of
bench/run.sh; the environment is listed first. The
machine is a shared cloud VM, so treat the figures as
order-of-magnitude comparisons between columns, not as absolute
numbers for your hardware. Reading the
results below says what to make of them.
- date: 2026-09-04T15:01:52Z
- cpu: Intel(R) Xeon(R) Processor @ 2.80GHz x4
- os: Linux 6.18.44-fc-v24 x86_64
- ocaml: 5.3.0
- rust: rustc 1.94.1 (e408947bf 2026-03-25) / aho-corasick 1.1.5
- python: 3.11.15
- pyahocorasick: 2.3.1
- ahocorasick_rs: 1.0.3
- seconds per measurement: 1
| workload | mode | OCaml | Rust default | Rust NFA | ahocorasick_rs | pyahocorasick | matches |
|---|---|---|---|---|---|---|---|
| sherlock/names-2 | overlapping | 174 | 9,636 | 203 | 7,618 | 231 | 558 |
| sherlock/names-2 | overlapping_iter | 176 | 558 | ||||
| sherlock/names-2 | standard | 182 | 11,071 | 214 | 7,075 | 558 | |
| sherlock/names-2 | leftmost_longest | 153 | 8,614 | 210 | 5,817 | 252 | 558 |
| sherlock/names-2 | is_match | early exit | early exit | early exit | found | ||
| sherlock/names-2 | replace | 135 | 7,447 | 593,047 | |||
| sherlock/names-2 | standard_stream | 189 | 280 | 558 | |||
| sherlock/names-2 | overlapping_stream | 171 | 558 | ||||
| sherlock/names-2 | leftmost_longest_stream | 153 | 558 | ||||
| sherlock/names-7 | overlapping | 173 | 524 | 202 | 514 | 140 | 740 |
| sherlock/names-7 | overlapping_iter | 171 | 740 | ||||
| sherlock/names-7 | standard | 176 | 527 | 208 | 513 | 740 | |
| sherlock/names-7 | leftmost_longest | 149 | 6,241 | 202 | 4,477 | 144 | 740 |
| sherlock/names-7 | is_match | early exit | early exit | early exit | found | ||
| sherlock/names-7 | replace | 131 | 5,406 | 592,628 | |||
| sherlock/names-7 | standard_stream | 186 | 282 | 740 | |||
| sherlock/names-7 | overlapping_stream | 176 | 740 | ||||
| sherlock/names-7 | leftmost_longest_stream | 143 | 740 | ||||
| sherlock/prefixes | overlapping | 178 | 10,511 | 205 | 7,570 | 235 | 582 |
| sherlock/prefixes | overlapping_iter | 179 | 582 | ||||
| sherlock/prefixes | standard | 193 | 11,900 | 213 | 7,347 | 582 | |
| sherlock/prefixes | leftmost_longest | 151 | 9,238 | 201 | 6,370 | 255 | 582 |
| sherlock/prefixes | is_match | early exit | early exit | early exit | found | ||
| sherlock/prefixes | replace | 141 | 7,053 | 594,818 | |||
| sherlock/prefixes | standard_stream | 193 | 280 | 582 | |||
| sherlock/prefixes | overlapping_stream | 182 | 582 | ||||
| sherlock/prefixes | leftmost_longest_stream | 150 | 582 | ||||
| sherlock/absent | overlapping | 178 | 87,180 | 203 | 88,701 | 168 | 0 |
| sherlock/absent | overlapping_iter | 178 | 0 | ||||
| sherlock/absent | standard | 198 | 87,385 | 209 | 89,501 | 0 | |
| sherlock/absent | leftmost_longest | 156 | 87,346 | 210 | 89,099 | 149 | 0 |
| sherlock/absent | is_match | 170 | 87,449 | 167 | 0 | ||
| sherlock/absent | replace | 144 | 24,953 | 594,915 | |||
| sherlock/absent | standard_stream | 199 | 254 | 0 | |||
| sherlock/absent | overlapping_stream | 180 | 0 | ||||
| sherlock/absent | leftmost_longest_stream | 154 | 0 | ||||
| sherlock/words-5000 | overlapping | 64.0 | 93.3 | 67.1 | 90.0 | 35.6 | 572 |
| sherlock/words-5000 | overlapping_iter | 63.0 | 572 | ||||
| sherlock/words-5000 | standard | 63.2 | 90.4 | 69.2 | 94.5 | 567 | |
| sherlock/words-5000 | leftmost_longest | 59.3 | 93.4 | 67.5 | 91.5 | 35.0 | 567 |
| sherlock/words-5000 | is_match | early exit | early exit | early exit | found | ||
| sherlock/words-5000 | replace | 53.2 | 86.6 | 592,533 | |||
| sherlock/words-5000 | standard_stream | 61.9 | 68.6 | 567 | |||
| sherlock/words-5000 | overlapping_stream | 64.5 | 572 | ||||
| sherlock/words-5000 | leftmost_longest_stream | 61.6 | 567 | ||||
| sherlock/words-15000 | overlapping | 45.9 | 86.4 | 53.0 | 79.5 | 28.6 | 2,241 |
| sherlock/words-15000 | overlapping_iter | 44.1 | 2,241 | ||||
| sherlock/words-15000 | standard | 46.9 | 85.8 | 58.9 | 82.3 | 2,100 | |
| sherlock/words-15000 | leftmost_longest | 44.1 | 85.6 | 55.7 | 82.4 | 29.4 | 2,100 |
| sherlock/words-15000 | is_match | early exit | early exit | early exit | found | ||
| sherlock/words-15000 | replace | 42.5 | 79.1 | 587,323 | |||
| sherlock/words-15000 | standard_stream | 49.7 | 58.7 | 2,100 | |||
| sherlock/words-15000 | overlapping_stream | 49.4 | 2,241 | ||||
| sherlock/words-15000 | leftmost_longest_stream | 47.7 | 2,100 | ||||
| opensubtitles/names-5 | overlapping | 171 | 536 | 202 | 516 | 159 | 714 |
| opensubtitles/names-5 | overlapping_iter | 169 | 714 | ||||
| opensubtitles/names-5 | standard | 180 | 538 | 209 | 514 | 714 | |
| opensubtitles/names-5 | leftmost_longest | 153 | 7,379 | 203 | 5,175 | 166 | 714 |
| opensubtitles/names-5 | is_match | early exit | early exit | early exit | found | ||
| opensubtitles/names-5 | replace | 139 | 5,897 | 890,243 | |||
| opensubtitles/names-5 | standard_stream | 199 | 286 | 714 | |||
| opensubtitles/names-5 | overlapping_stream | 180 | 714 | ||||
| opensubtitles/names-5 | leftmost_longest_stream | 153 | 714 | ||||
| opensubtitles/dictionary-15 | overlapping | 82.3 | 95.8 | 73.4 | 94.3 | 37.5 | 15 |
| opensubtitles/dictionary-15 | overlapping_iter | 83.6 | 15 | ||||
| opensubtitles/dictionary-15 | standard | 86.4 | 92.9 | 73.5 | 98.9 | 15 | |
| opensubtitles/dictionary-15 | leftmost_longest | 83.9 | 97.7 | 71.3 | 94.6 | 34.5 | 15 |
| opensubtitles/dictionary-15 | is_match | early exit | early exit | early exit | found | ||
| opensubtitles/dictionary-15 | replace | 78.9 | 97.0 | 899,085 | |||
| opensubtitles/dictionary-15 | standard_stream | 85.3 | 70.0 | 15 | |||
| opensubtitles/dictionary-15 | overlapping_stream | 82.8 | 15 | ||||
| opensubtitles/dictionary-15 | leftmost_longest_stream | 82.7 | 15 | ||||
| opensubtitles/byte-a | overlapping | 83.9 | 789 | 167 | 140 | 146 | 47,062 |
| opensubtitles/byte-a | overlapping_iter | 133 | 47,062 | ||||
| opensubtitles/byte-a | standard | 85.9 | 779 | 168 | 131 | 47,062 | |
| opensubtitles/byte-a | leftmost_longest | 75.6 | 782 | 48.8 | 130 | 142 | 47,062 |
| opensubtitles/byte-a | is_match | early exit | early exit | early exit | found | ||
| opensubtitles/byte-a | replace | 42.8 | 439 | 993,356 | |||
| opensubtitles/byte-a | standard_stream | 144 | 189 | 47,062 | |||
| opensubtitles/byte-a | overlapping_stream | 143 | 47,062 | ||||
| opensubtitles/byte-a | leftmost_longest_stream | 121 | 47,062 | ||||
| opensubtitles/bytes-8 | overlapping | 18.2 | 115 | 84.5 | 16.5 | 32.3 | 309,829 |
| opensubtitles/bytes-8 | overlapping_iter | 58.4 | 309,829 | ||||
| opensubtitles/bytes-8 | standard | 19.9 | 81.2 | 73.6 | 15.7 | 309,829 | |
| opensubtitles/bytes-8 | leftmost_longest | 18.8 | 90.4 | 10.3 | 15.4 | 30.6 | 309,829 |
| opensubtitles/bytes-8 | is_match | early exit | early exit | early exit | found | ||
| opensubtitles/bytes-8 | replace | 8.9 | 55.7 | 1,518,890 | |||
| opensubtitles/bytes-8 | standard_stream | 66.7 | 68.6 | 309,829 | |||
| opensubtitles/bytes-8 | overlapping_stream | 63.6 | 309,829 | ||||
| opensubtitles/bytes-8 | leftmost_longest_stream | 51.4 | 309,829 | ||||
| dense/abc-1m | overlapping | 2.5 | 43.3 | 23.4 | 1.8 | 5.4 | 3,145,725 |
| dense/abc-1m | overlapping_iter | 27.1 | 3,145,725 | ||||
| dense/abc-1m | standard | 7.5 | 30.2 | 40.3 | 5.3 | 1,048,576 | |
| dense/abc-1m | leftmost_longest | 16.4 | 74.7 | 10.5 | 15.4 | 31.0 | 349,526 |
| dense/abc-1m | is_match | early exit | early exit | early exit | found | ||
| dense/abc-1m | replace | 8.7 | 73.9 | 1,398,103 | |||
| dense/abc-1m | standard_stream | 52.2 | 67.6 | 1,048,576 | |||
| dense/abc-1m | overlapping_stream | 11.5 | 3,145,725 | ||||
| dense/abc-1m | leftmost_longest_stream | 37.3 | 349,526 | ||||
| sparse/bytes-1m | overlapping | 152 | 490 | 127 | 492 | 39.8 | 0 |
| sparse/bytes-1m | overlapping_iter | 149 | 0 | ||||
| sparse/bytes-1m | standard | 156 | 486 | 127 | 501 | 0 | |
| sparse/bytes-1m | leftmost_longest | 138 | 496 | 128 | 489 | 38.8 | 0 |
| sparse/bytes-1m | is_match | 149 | 495 | 40.3 | 0 | ||
| sparse/bytes-1m | replace | 127 | 465 | 1,048,576 | |||
| sparse/bytes-1m | standard_stream | 156 | 275 | 0 | |||
| sparse/bytes-1m | overlapping_stream | 148 | 0 | ||||
| sparse/bytes-1m | leftmost_longest_stream | 140 | 0 | ||||
| pathological/suffixes | overlapping | 0.4 | 7.2 | 1.5 | 0.3 | 0.9 | 3,999,810 |
| pathological/suffixes | overlapping_iter | 4.6 | 3,999,810 | ||||
| pathological/suffixes | standard | 9.3 | 30.4 | 41.4 | 5.6 | 200,000 | |
| pathological/suffixes | leftmost_longest | 11.1 | 240 | 53.3 | 113 | 150 | 10,000 |
| pathological/suffixes | is_match | early exit | early exit | early exit | found | ||
| pathological/suffixes | replace | 10.0 | 227 | 40,000 | |||
| pathological/suffixes | standard_stream | 51.6 | 68.1 | 200,000 | |||
| pathological/suffixes | overlapping_stream | 0.7 | 3,999,810 | ||||
| pathological/suffixes | leftmost_longest_stream | 10.5 | 10,000 | ||||
| scaling/abc-64k | overlapping | 3.3 | 45.9 | 27.5 | 196,605 | ||
| scaling/abc-64k | overlapping_iter | 26.3 | 196,605 | ||||
| scaling/abc-64k | standard | 12.8 | 31.5 | 42.1 | 65,536 | ||
| scaling/abc-64k | leftmost_longest | 23.8 | 76.5 | 10.3 | 21,846 | ||
| scaling/abc-64k | is_match | early exit | early exit | found | |||
| scaling/abc-64k | replace | 8.9 | 74.4 | 87,383 | |||
| scaling/abc-64k | standard_stream | 50.2 | 68.1 | 65,536 | |||
| scaling/abc-64k | overlapping_stream | 10.3 | 196,605 | ||||
| scaling/abc-64k | leftmost_longest_stream | 38.6 | 21,846 | ||||
| scaling/abc-256k | overlapping | 2.6 | 44.6 | 25.4 | 786,429 | ||
| scaling/abc-256k | overlapping_iter | 26.3 | 786,429 | ||||
| scaling/abc-256k | standard | 8.2 | 30.5 | 41.8 | 262,144 | ||
| scaling/abc-256k | leftmost_longest | 13.4 | 74.9 | 9.9 | 87,382 | ||
| scaling/abc-256k | is_match | early exit | early exit | found | |||
| scaling/abc-256k | replace | 9.6 | 67.1 | 349,527 | |||
| scaling/abc-256k | standard_stream | 53.0 | 68.7 | 262,144 | |||
| scaling/abc-256k | overlapping_stream | 10.7 | 786,429 | ||||
| scaling/abc-256k | leftmost_longest_stream | 36.8 | 87,382 | ||||
| scaling/abc-1m | overlapping | 2.4 | 45.2 | 27.6 | 3,145,725 | ||
| scaling/abc-1m | overlapping_iter | 26.6 | 3,145,725 | ||||
| scaling/abc-1m | standard | 7.9 | 29.1 | 40.5 | 1,048,576 | ||
| scaling/abc-1m | leftmost_longest | 16.2 | 74.7 | 10.4 | 349,526 | ||
| scaling/abc-1m | is_match | early exit | early exit | found | |||
| scaling/abc-1m | replace | 8.7 | 73.5 | 1,398,103 | |||
| scaling/abc-1m | standard_stream | 53.8 | 69.6 | 1,048,576 | |||
| scaling/abc-1m | overlapping_stream | 11.7 | 3,145,725 | ||||
| scaling/abc-1m | leftmost_longest_stream | 37.2 | 349,526 | ||||
| scaling/abc-4m | overlapping | 1.5 | 45.4 | 27.6 | 12,582,909 | ||
| scaling/abc-4m | overlapping_iter | 26.9 | 12,582,909 | ||||
| scaling/abc-4m | standard | 7.3 | 30.7 | 41.7 | 4,194,304 | ||
| scaling/abc-4m | leftmost_longest | 15.3 | 74.4 | 10.4 | 1,398,102 | ||
| scaling/abc-4m | is_match | early exit | early exit | found | |||
| scaling/abc-4m | replace | 7.2 | 65.5 | 5,592,407 | |||
| scaling/abc-4m | standard_stream | 45.5 | 66.6 | 4,194,304 | |||
| scaling/abc-4m | overlapping_stream | 12.3 | 12,582,909 | ||||
| scaling/abc-4m | leftmost_longest_stream | 37.3 | 1,398,102 |
| workload | recorded | measured | result |
|---|---|---|---|
| sherlock/names-2 | 558 | 558 | reproduced |
| sherlock/names-7 | 740 | 740 | reproduced |
| sherlock/prefixes | 582 | 582 | reproduced |
| sherlock/absent | 0 | 0 | reproduced |
| opensubtitles/names-5 | 714 | 714 | reproduced |
| opensubtitles/dictionary-15 | 15 | 15 | reproduced |
| opensubtitles/byte-a | 47,062 | 47,062 | reproduced |
| opensubtitles/bytes-8 | 309,829 | 309,829 | reproduced |
| workload | OCaml | Rust default | Rust NFA | ahocorasick_rs | pyahocorasick |
|---|---|---|---|---|---|
| sherlock/names-2 | 0.02 | 0.09 | 0.04 | 1.28 | 0.30 |
| sherlock/names-7 | 0.15 | 0.07 | 0.04 | 0.85 | 0.36 |
| sherlock/prefixes | 0.02 | 0.05 | 0.03 | 0.94 | 0.39 |
| sherlock/absent | 0.11 | 0.10 | 0.04 | 0.97 | 0.39 |
| sherlock/words-5000 | 43.43 | 6.55 | 5.34 | 13.63 | 7.37 |
| sherlock/words-15000 | 104.07 | 18.67 | 19.43 | 40.75 | 24.02 |
| opensubtitles/names-5 | 0.17 | 0.08 | 0.04 | 0.99 | 0.30 |
| opensubtitles/dictionary-15 | 29.10 | 3.59 | 3.22 | 8.44 | 4.16 |
| opensubtitles/byte-a | 0.01 | 0.04 | 0.04 | 0.81 | 0.32 |
| opensubtitles/bytes-8 | 0.02 | 0.04 | 0.03 | 0.93 | 0.39 |
| dense/abc-1m | 0.09 | 0.08 | 0.09 | 0.98 | 0.33 |
| sparse/bytes-1m | 2.04 | 0.89 | 0.20 | 2.40 | 0.47 |
| pathological/suffixes | 0.04 | 0.09 | 0.04 | 0.86 | 0.31 |
| scaling/abc-64k | 0.13 | 0.06 | 0.06 | ||
| scaling/abc-256k | 0.11 | 0.07 | 0.05 | ||
| scaling/abc-1m | 0.09 | 0.07 | 0.05 | ||
| scaling/abc-4m | 0.09 | 0.08 | 0.05 |
| workload | bytes | OCaml overlapping | OCaml standard | OCaml leftmost_longest | Rust default overlapping | Rust default standard | Rust default leftmost_longest |
|---|---|---|---|---|---|---|---|
| scaling/abc-64k | 65,536 | 304.35 | 77.97 | 41.97 | 21.77 | 31.78 | 13.07 |
| scaling/abc-256k | 262,144 | 389.65 | 122.52 | 74.51 | 22.42 | 32.80 | 13.35 |
| scaling/abc-1m | 1,048,576 | 409.39 | 125.96 | 61.57 | 22.15 | 34.41 | 13.39 |
| scaling/abc-4m | 4,194,304 | 679.14 | 136.17 | 65.51 | 22.05 | 32.59 | 13.44 |
Reading the results
Ordinary text, few patterns
On English text where matches are rare — the Sherlock and OpenSubtitles name workloads — this library scans at 170 to 200 MB/s, within fifteen percent of the Rust crate's bare NFA and close to pyahocorasick's C engine. The Rust crate's default configuration is up to sixty times faster on the two- and three-pattern rows because it is not running an automaton at all: its Teddy prefilter looks for candidate bytes with SIMD instructions and only confirms hits with the automaton. That is the gap between a dependency-free OCaml automaton and a vectorised scanner, and it narrows as soon as the prefilter stops paying off: with seven names the default engine is at about 530 MB/s, and with 5 000 or 15 000 words under 100.
Dictionaries
With thousands of patterns the automaton is large and the
prefilter is gone, so every engine is bounded by how fast it walks
the trie. This library reaches 46 MB/s on 15 000 words
and 82 to 86 MB/s on the 2 663 long words of
dictionary-15: level with or ahead of the Rust NFA,
and between half and nine tenths of the Rust default. The first
4096 nodes in breadth-first order have dense rows; beyond them a
sorted-children lookup and failure links take over, which is where
the remaining gap sits. Construction is where the library is
clearly slower: about 100 ms for 15 000 words against
the crate's 19, spent filling the dense rows and building the trie
through a hash table. It happens once.
Match-dense inputs
When almost every byte ends a match, reporting dominates scanning.
find_all returns a list of records, so on
dense/abc-1m, with three matches per byte, it keeps
three million records live and the garbage collector's work shows:
2.5 MB/s, against 27 MB/s for find_iter,
whose records die young, and 43 MB/s for the Rust crate,
which allocates nothing. The API
page's advice stands: consume dense matches lazily. The
leftmost-longest selection no longer pays that cost at all; at
16 MB/s on the same input it is ahead of the Rust NFA's 10,
and its streamed form reaches 37 MB/s, a mode the Rust crate
does not offer on streams. The pathological row, twenty nested
patterns over a run of the same byte, is the same story at twenty
matches per byte.
Streams
Feeding the input in 4 KiB chunks costs this library nothing:
Stream.feed_nonoverlapping runs at 190 to
200 MB/s on the name workloads, the same as the whole-input
search, and faster on dense inputs, where each chunk's result list
is short-lived. The Rust crate's stream_find_iter
reads through a buffer and runs at 250 to 290 MB/s however
fast its whole-input search is, so on streams the two are within
a factor of two everywhere and level on the dictionary rows.
Linear time
The scaling rows run the same 39 patterns over 64 KiB to
4 MiB of the same random text. find_iter holds at
26 to 27 MB/s across the four sizes: the automaton's cost per
byte is constant, as the amortised
argument says it must be. The list-returning modes slow down as
the input grows — find_all from 304 to 679
nanoseconds per byte — because their result grows with it
and a larger live heap makes each collection dearer. That is the
price of the result, not of the scan.
Before and after
The first run of this suite was against the version of the library that stored transitions in a hash table and selected leftmost-longest matches by materialising every overlapping match and sorting. Those numbers are the reason the transition table and the selection were rewritten (the cost section of the API page describes what the library does now); the semantics did not change, which the differential test confirmed byte for byte before and after.
| workload | mode | before, MB/s | after, MB/s | ratio |
|---|---|---|---|---|
| sherlock/names-2 | overlapping | 51.9 | 174 | 3.4x |
| sherlock/names-2 | overlapping_iter | 49.8 | 176 | 3.5x |
| sherlock/names-2 | standard | 63.3 | 182 | 2.9x |
| sherlock/names-2 | leftmost_longest | 52.8 | 153 | 2.9x |
| sherlock/names-2 | replace | 43.4 | 135 | 3.1x |
| sherlock/names-2 | standard_stream | 65.3 | 189 | 2.9x |
| sherlock/names-2 | leftmost_longest_stream | 43.4 | 153 | 3.5x |
| sherlock/words-15000 | overlapping | 9.5 | 45.9 | 4.8x |
| sherlock/words-15000 | overlapping_iter | 10.4 | 44.1 | 4.2x |
| sherlock/words-15000 | standard | 10.7 | 46.9 | 4.4x |
| sherlock/words-15000 | leftmost_longest | 9.1 | 44.1 | 4.8x |
| sherlock/words-15000 | replace | 9.6 | 42.5 | 4.4x |
| sherlock/words-15000 | standard_stream | 10.3 | 49.7 | 4.8x |
| sherlock/words-15000 | leftmost_longest_stream | 9.6 | 47.7 | 4.9x |
| opensubtitles/dictionary-15 | overlapping | 12.8 | 82.3 | 6.4x |
| opensubtitles/dictionary-15 | overlapping_iter | 12.2 | 83.6 | 6.9x |
| opensubtitles/dictionary-15 | standard | 12.6 | 86.4 | 6.9x |
| opensubtitles/dictionary-15 | leftmost_longest | 12.6 | 83.9 | 6.7x |
| opensubtitles/dictionary-15 | replace | 11.5 | 78.9 | 6.9x |
| opensubtitles/dictionary-15 | standard_stream | 12.6 | 85.3 | 6.8x |
| opensubtitles/dictionary-15 | leftmost_longest_stream | 12.1 | 82.7 | 6.8x |
| opensubtitles/bytes-8 | overlapping | 11.9 | 18.2 | 1.5x |
| opensubtitles/bytes-8 | overlapping_iter | 26.2 | 58.4 | 2.2x |
| opensubtitles/bytes-8 | standard | 15.6 | 19.9 | 1.3x |
| opensubtitles/bytes-8 | leftmost_longest | 4.9 | 18.8 | 3.8x |
| opensubtitles/bytes-8 | replace | 3.9 | 8.9 | 2.3x |
| opensubtitles/bytes-8 | standard_stream | 32.7 | 66.7 | 2.0x |
| opensubtitles/bytes-8 | leftmost_longest_stream | 0.7 | 51.4 | 77.1x |
| dense/abc-1m | overlapping | 1.7 | 2.5 | 1.4x |
| dense/abc-1m | overlapping_iter | 11.6 | 27.1 | 2.3x |
| dense/abc-1m | standard | 6.4 | 7.5 | 1.2x |
| dense/abc-1m | leftmost_longest | 0.6 | 16.4 | 27.0x |
| dense/abc-1m | replace | 0.6 | 8.7 | 14.5x |
| dense/abc-1m | standard_stream | 16.8 | 52.2 | 3.1x |
| dense/abc-1m | leftmost_longest_stream | 0.1 | 37.3 | 732.0x |
| sparse/bytes-1m | overlapping | 18.3 | 152 | 8.3x |
| sparse/bytes-1m | overlapping_iter | 17.2 | 149 | 8.7x |
| sparse/bytes-1m | standard | 19.4 | 156 | 8.0x |
| sparse/bytes-1m | leftmost_longest | 18.4 | 138 | 7.5x |
| sparse/bytes-1m | replace | 16.7 | 127 | 7.6x |
| sparse/bytes-1m | standard_stream | 18.9 | 156 | 8.3x |
| sparse/bytes-1m | leftmost_longest_stream | 17.8 | 140 | 7.9x |
| pathological/suffixes | overlapping | 0.4 | 0.4 | 1.1x |
| pathological/suffixes | overlapping_iter | 2.6 | 4.6 | 1.7x |
| pathological/suffixes | standard | 7.4 | 9.3 | 1.2x |
| pathological/suffixes | leftmost_longest | 0.1 | 11.1 | 138.1x |
| pathological/suffixes | replace | 0.1 | 10.0 | 129.2x |
| pathological/suffixes | standard_stream | 23.4 | 51.6 | 2.2x |
| pathological/suffixes | leftmost_longest_stream | 0.0 | 10.5 | 740.2x |
What is not measured
- Memory
- Automaton size and peak allocation during a scan are not recorded. The dense transition rows cost 2 KiB per node for up to 4096 nodes; a 15 000-word automaton is a few megabytes.
- Latency
- Streamed modes are timed for throughput over 4 KiB chunks, not for the delay between a byte arriving and a match being reported, which the semantics page bounds rather than measures.
- Small inputs
-
Every haystack is at least 200 KB, so per-call overhead
— the cost of
find_allon a 40-byte log line — does not show. - Other implementations
-
Hyperscan, daachorse and the Go and Java libraries are not in the
table. The drivers speak the format described in
bench/README.md; adding one is a matter of writing a driver.
./bench/run.sh reproduces everything on this page, and
bench/README.md documents the workloads and the
method. Back to correctness, or on
to compatibility.