[performance-profiler] Bucket alt-prefix literals by first byte in libbeat/common/match hot path
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 12.7k
- Forks
- 5k
- Avg merge
- 2d 1h
- Merged PRs (30d)
- 370
Description
Hot Path
libbeat/common/match/matchers.go:152-176 in (*altPrefixMatcher).MatchString and (*altPrefixMatcher).Match performs linear scans across all prefix literals.
Construction path is in libbeat/common/match/compile.go:53-59 (isPrefixAltLiterals case), which currently feeds all literals into one matcher list.
Profiling Data
Before (same benchmark command):
go test -run '^$' -bench '^BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level$' -benchmem -count=5 ./libbeat/common/match
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 1024 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 634.8 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 849.1 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 597.1 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 642.8 ns/op
CPU profile sample from the same path showed (*altPrefixMatcher).Match as a top consumer.
Proposed Change
Build a first-byte dispatch table for alt-prefix literals, then only scan candidates that share the input’s first byte.
Representative change shape:
- Add constructor to pre-bucket literals by
literals[i][0] - In
MatchString/Match, switch from scanningm.literalsto scanningm.literalsByFirst[input[0]]when available - Wire compile path to use the new constructor (
libbeat/common/match/compile.go:53-59)
Results
After (same benchmark command):
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 221.6 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 208.7 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 210.7 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 207.9 ns/op
BenchmarkPatterns/Name=startsWithLevel,_Matcher=Match,_Content=simple_log_with_level-4 ... 269.1 ns/op
Improvement: ~70.17% faster average ns/op (749.56 → 223.60 ns/op).
Verification
- Focused package tests:
go test ./libbeat/common/matchpassed. - Behavior-preserving approach: matching logic is unchanged except candidate-set narrowing by first byte.
Evidence
- Benchmark command was identical before/after.
- Before/after outputs are clearly different.
- Duplicate check:
- Compared against
/tmp/previous-findings.jsonopen performance-profiler issues (no matchingaltPrefixMatcher/first-byte-dispatch item). - Queried open issues in
elastic/beatsfor related terms; no matching open issue found.
- Compared against
What is this? | From workflow: Performance Profiler
Give us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.
- expires on Aug 28, 2026, 2:26 PM UTC
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with libbeat/common/match/matchers.go:152-176 to trace altPrefixMatcher.MatchString and Match, then inspect the construction path in libbeat/common/match/compile.go:53-59. Run the focused package tests and the provided BenchmarkPatterns command. Done means first-byte candidate bucketing preserves matching behavior and shows the reported benchmark improvement.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- performance
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100