Skip to content

Speed up lookup by ~13% (template callbacks + root dispatch table)#17

Merged
yhirose merged 1 commit into
masterfrom
optimize-lookup
Jul 13, 2026
Merged

Speed up lookup by ~13% (template callbacks + root dispatch table)#17
yhirose merged 1 commit into
masterfrom
optimize-lookup

Conversation

@yhirose

@yhirose yhirose commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Two format-neutral optimizations to the read path. The compiled byte code is unchanged (byte-identical output; existing .fst files stay fully compatible), so this only touches how a matcher traverses it.

  • Template callbacks in matcher::match. The output/prefix callbacks were std::function, so every query constructed a std::function and called through it indirectly. They are now template parameters (with std::nullptr_t defaults and if constexpr guards), so nothing is allocated per query and the calls inline. map::common_prefix_search / set::common_prefix_search are templatized to match (lambda callers are unaffected).
  • Root dispatch table. Every query passes through the root state, paying a jump-table binary search on the first character. Each matcher now precomputes a 256-entry label -> address table for the root at construction time, making that step O(1). Costs ~1 KB of memory per matcher; the file is untouched.

Measurements

benchmark/main.cc on /usr/share/dict/words (235,976 keys, Apple M1 Pro), min of 12 runs:

metric before after Δ
exact match (map) 217 ms 189 ms −12.9%
common prefix (map) 217 ms 189 ms −12.9%
exact match (keys) 223 ms 196 ms −12.1%
common prefix (keys) 223 ms 196 ms −12.1%

Compiled FST size is identical before/after (1,070,382 bytes for map<uint32_t>), confirming the byte code is unchanged.

Testing

  • ctest: 36/36 pass.

…able

Two format-neutral changes to the read path (the byte code is
unchanged and stays fully compatible):

- matcher::match takes its output/prefix callbacks as template
  parameters instead of std::function, so no std::function is
  constructed per query and the calls inline. exact/common-prefix
  search on /usr/share/dict/words drop ~13%.

- Each matcher precomputes a 256-entry label->address dispatch table
  for the root state at construction time, replacing the root's jump
  table binary search (every query passes through the root) with an
  O(1) lookup. Costs 1KB of memory per matcher; the file is untouched.
@yhirose
yhirose merged commit 98cc89a into master Jul 13, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant