Skip to content
This repository was archived by the owner on Aug 2, 2026. It is now read-only.

⚡ Bolt: Implement fast path for lexer keyword lookup - #39

Open
srimon12 wants to merge 1 commit into
mainfrom
bolt/lexer-lookup-fast-2134823455075652399
Open

⚡ Bolt: Implement fast path for lexer keyword lookup#39
srimon12 wants to merge 1 commit into
mainfrom
bolt/lexer-lookup-fast-2134823455075652399

Conversation

@srimon12

@srimon12 srimon12 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

💡 What: Replaced the lexer's lookupKeyword map lookup with a generated lookupKeywordFast function that performs length-based switch comparisons.
🎯 Why: The lexer performs keyword lookups very frequently. A map lookup involves hashing, even when optimized by Go to avoid memory allocations map[string(byte_slice)]. Using a perfectly sized static switch statement drastically reduces lookup overhead.
📊 Impact: Reduces lookup latency by roughly 2.5x. Benchmarks improved from ~74ns to ~29ns per operation. Overall Tokenize improved.
🔬 Measurement: Verify by running cd internal/lexer && go test -bench=BenchmarkLookupKeyword.


PR created automatically by Jules for task 2134823455075652399 started by @srimon12

Summary by CodeRabbit

  • Performance Improvements

    • Improved keyword recognition during tokenization, making lexer processing faster for common queries.
    • Added a faster lookup path that should reduce latency in parsing workloads.
  • Tests

    • Added benchmarks to measure tokenization and keyword lookup performance.
    • Included allocation-focused benchmarking to help track efficiency over time.

Generates an optimized switch statement matching keywords by length and case-insensitively, avoiding the overhead of map lookup even when the map lookup allocates no memory. Evaluated benchmarks show ~2.5x speedup for lookups. Fallback to `keywords` map is retained for safety. Added `gen.go` to re-generate when keywords map changes.

Co-authored-by: srimon12 <33979603+srimon12@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a go:generate-driven code generator (gen_lookup.go) that parses the existing keyword map in lexer.go, groups keywords by length, and emits lookup_fast.go containing a length-based switch with case-insensitive byte comparisons. lookupKeyword is updated to call lookupKeywordFast as a fast path. Benchmarks for tokenization and keyword lookup are added.

Changes

Lexer fast keyword lookup

Layer / File(s) Summary
Code generator
internal/lexer/gen.go, internal/lexer/gen_lookup.go
gen.go adds the go:generate directive; gen_lookup.go reads lexer.go, extracts keyword-to-token mappings, groups by keyword length, and writes a formatted lookup_fast.go with a switch len(s) dispatch and an equalsCaseInsensitive helper.
Generated implementation and lexer integration
internal/lexer/lookup_fast.go, internal/lexer/lexer.go
lookup_fast.go contains the generated lookupKeywordFast and equalsCaseInsensitive functions. lexer.go adds a fast-path early return in lookupKeyword that calls lookupKeywordFast before falling back to the existing map logic.
Benchmarks
internal/lexer/benchmark_test.go, internal/lexer/lookup_test.go
Adds BenchmarkTokenize, BenchmarkLookupKeyword, BenchmarkLookupKeywordAlloc, and BenchmarkLookupKeywordFast to compare map-based and switch-based keyword lookup performance.
Notes
.jules/bolt.md
Records investigation notes on allocation behavior and the ~2.5x speedup from the generated switch.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐇 A map was fast, but switch is faster,
With lengths and bytes, no allocation disaster!
The generator runs, the file appears,
Each keyword found in nanoseconds clear.
Hop hop hooray, benchmarks agree—
The lexer now flies, wild and free! 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a fast path for lexer keyword lookup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt/lexer-lookup-fast-2134823455075652399

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
internal/lexer/gen_lookup.go (2)

16-45: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add a sync guard so the committed lookup_fast.go can't silently drift from keywords.

Since lookup_fast.go is checked in and lookupKeyword only falls back to the keywords map when the fast path misses, a stale generated file would not cause a hard failure — it would just silently bypass the optimization (or, worse, return a wrong kind if a mapping ever changed). A cheap table-driven test that asserts lookupKeywordFast agrees with the keywords map for every entry would catch drift in CI.

func TestLookupKeywordFastInSync(t *testing.T) {
	for kw, want := range keywords {
		if got, ok := lookupKeywordFast(kw); !ok || got != want {
			t.Errorf("lookupKeywordFast(%q) = (%v,%v); want (%v,true)", kw, got, ok, want)
		}
	}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/gen_lookup.go` around lines 16 - 45, The committed fast
keyword lookup can drift from the source keywords without failing, so add a sync
guard in tests to keep them aligned. Create a table-driven test around
lookupKeywordFast that iterates over the keywords map and asserts each entry
returns the same TokenKind and a true ok result, using the existing
lookupKeywordFast and keywords symbols so CI catches mismatches if the generated
lookup_fast.go falls out of sync.

28-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Parsing is brittle (as the comment notes); consider keying off the keyword quotes.

break on the first line containing } and SplitN(line, ":", 2) happen to work for the current map, but any inline comment, a } in a trailing comment, or a reformatting would silently truncate or mis-parse the keyword set. Gating on lines that actually start with a quoted key (e.g. require kw to be non-empty and the line to be a "...": ..., entry) would make regeneration more robust.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/gen_lookup.go` around lines 28 - 45, The keyword map parsing
in gen_lookup.go is too brittle because it stops on any line containing “}” and
accepts any split on “:”, which can mis-parse or truncate the keyword list.
Update the parsing logic in the lookup generation flow (the map scan around
inMap and keywords append) to only consume lines that actually begin with a
quoted keyword entry, and only terminate when you reach the real map closing
brace, so inline comments or reformatting do not affect regeneration.
internal/lexer/lookup_fast.go (2)

4-313: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

The fast path is a linear scan within each length bucket, not a "perfect switch."

Despite the PR/code wording, each case does a sequential chain of equalsCaseInsensitive calls (e.g. case 6 walks ~24 comparisons worst case). It's still likely faster than a map hash on short keys, but if the benchmark gain matters, a secondary switch s[0] (uppercased) inside each length bucket would cut the comparisons to ~1. This is a generator change in gen_lookup.go, not a hand-edit here (file is DO NOT EDIT).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/lookup_fast.go` around lines 4 - 313, The current fast path in
lookupKeywordFast is only a linear chain of equalsCaseInsensitive checks per
length bucket, so update the generator instead of hand-editing this file. In
gen_lookup.go, add a secondary dispatch by the first character (normalized for
case) inside each length-based bucket so lookupKeywordFast can narrow candidates
before calling equalsCaseInsensitive, which reduces the worst-case comparisons
while preserving TokenKind mappings. Keep the generated lookupKeywordFast
signature and all existing keyword/token names intact.

315-329: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

equalsCaseInsensitive duplicates hasPrefixCaseInsensitive in lexer.go.

Both implement the same ASCII uppercase-and-compare loop; the only difference is the leading length check (already guaranteed here by the switch len(s)). Worth consolidating onto one helper to avoid two divergent copies of the case-folding rule — though since this file is generated, the dedup would have to come from the generator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/lookup_fast.go` around lines 315 - 329, The ASCII case-folding
logic in equalsCaseInsensitive duplicates hasPrefixCaseInsensitive, so
consolidate both through a single shared helper in the generator that performs
the uppercase-and-compare loop. Update the generated lexer code path that emits
equalsCaseInsensitive and hasPrefixCaseInsensitive to call the same reusable
implementation, keeping the existing len(s) guard where needed and ensuring only
one source of truth for the case-insensitive comparison rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/lexer/benchmark_test.go`:
- Around line 22-25: The tokenize benchmark is ignoring both return values from
Tokenize, so it can benchmark an error path instead of real tokenization work.
Update the benchmark loop in the benchmark function to capture the tokens and
error from Tokenize, keep the token result alive so it is not optimized away,
and call b.Fatal on any error inside the loop to ensure the benchmark stays on
the success path.

In `@internal/lexer/lookup_test.go`:
- Around line 7-31: The benchmarks in lookup_test.go are discarding the
TokenKind and bool results from lookupKeyword and lookupKeywordFast, which can
skew the measurements and hide invalid keyword misses. Update
BenchmarkLookupKeyword, BenchmarkLookupKeywordAlloc, and
BenchmarkLookupKeywordFast to assign the returned values to package-level sink
variables, and add validation that panics or fails the benchmark if ok is false.
Use the existing benchmark functions and the lookupKeyword/lookupKeywordFast
symbols to keep the fix localized.

---

Nitpick comments:
In `@internal/lexer/gen_lookup.go`:
- Around line 16-45: The committed fast keyword lookup can drift from the source
keywords without failing, so add a sync guard in tests to keep them aligned.
Create a table-driven test around lookupKeywordFast that iterates over the
keywords map and asserts each entry returns the same TokenKind and a true ok
result, using the existing lookupKeywordFast and keywords symbols so CI catches
mismatches if the generated lookup_fast.go falls out of sync.
- Around line 28-45: The keyword map parsing in gen_lookup.go is too brittle
because it stops on any line containing “}” and accepts any split on “:”, which
can mis-parse or truncate the keyword list. Update the parsing logic in the
lookup generation flow (the map scan around inMap and keywords append) to only
consume lines that actually begin with a quoted keyword entry, and only
terminate when you reach the real map closing brace, so inline comments or
reformatting do not affect regeneration.

In `@internal/lexer/lookup_fast.go`:
- Around line 4-313: The current fast path in lookupKeywordFast is only a linear
chain of equalsCaseInsensitive checks per length bucket, so update the generator
instead of hand-editing this file. In gen_lookup.go, add a secondary dispatch by
the first character (normalized for case) inside each length-based bucket so
lookupKeywordFast can narrow candidates before calling equalsCaseInsensitive,
which reduces the worst-case comparisons while preserving TokenKind mappings.
Keep the generated lookupKeywordFast signature and all existing keyword/token
names intact.
- Around line 315-329: The ASCII case-folding logic in equalsCaseInsensitive
duplicates hasPrefixCaseInsensitive, so consolidate both through a single shared
helper in the generator that performs the uppercase-and-compare loop. Update the
generated lexer code path that emits equalsCaseInsensitive and
hasPrefixCaseInsensitive to call the same reusable implementation, keeping the
existing len(s) guard where needed and ensuring only one source of truth for the
case-insensitive comparison rules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a6e32caa-bb4f-4755-8275-aef290956002

📥 Commits

Reviewing files that changed from the base of the PR and between 7843d01 and d321f3f.

📒 Files selected for processing (7)
  • .jules/bolt.md
  • internal/lexer/benchmark_test.go
  • internal/lexer/gen.go
  • internal/lexer/gen_lookup.go
  • internal/lexer/lexer.go
  • internal/lexer/lookup_fast.go
  • internal/lexer/lookup_test.go

Comment on lines +22 to +25
b.ResetTimer()
for i := 0; i < b.N; i++ {
lex.Tokenize(query)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the tokenize benchmark on the success path.

Tokenize returns ([]Token, error), but this loop drops both values. If the sample query ever starts failing, the benchmark will still pass and may report a faster error path instead of real tokenization work. Sink the tokens and b.Fatal on err inside the loop.

Proposed fix
 package lexer
 
 import (
 	"strings"
 	"testing"
 )
+
+var benchmarkTokens []Token
 
 func BenchmarkTokenize(b *testing.B) {
 	lex := &Lexer{}
 	query := `SELECT id, _score, VECTOR, VECTOR[my_vec], PAYLOAD, PAYLOAD.city,
 		PAYLOAD[location],
@@
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		lex.Tokenize(query)
+		tokens, err := lex.Tokenize(query)
+		if err != nil {
+			b.Fatal(err)
+		}
+		benchmarkTokens = tokens
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
b.ResetTimer()
for i := 0; i < b.N; i++ {
lex.Tokenize(query)
}
package lexer
import (
"strings"
"testing"
)
var benchmarkTokens []Token
func BenchmarkTokenize(b *testing.B) {
lex := &Lexer{}
query := `SELECT id, _score, VECTOR, VECTOR[my_vec], PAYLOAD, PAYLOAD.city,
PAYLOAD[location],
MAX(values), SUM(INT_TO_FLOAT(score) * values) FROM index_name
WHERE payload.city = 'ABC' OR value = 1 OR city = 'XYZ' AND _score > 0
ORDER BY score DESC LIMIT 10`
b.ResetTimer()
for i := 0; i < b.N; i++ {
tokens, err := lex.Tokenize(query)
if err != nil {
b.Fatal(err)
}
benchmarkTokens = tokens
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/benchmark_test.go` around lines 22 - 25, The tokenize
benchmark is ignoring both return values from Tokenize, so it can benchmark an
error path instead of real tokenization work. Update the benchmark loop in the
benchmark function to capture the tokens and error from Tokenize, keep the token
result alive so it is not optimized away, and call b.Fatal on any error inside
the loop to ensure the benchmark stays on the success path.

Comment on lines +7 to +31
func BenchmarkLookupKeyword(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeyword("select")
lookupKeyword("where")
lookupKeyword("limit")
}
}

func BenchmarkLookupKeywordAlloc(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeyword("select")
}
}

func BenchmarkLookupKeywordFast(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeywordFast("select")
lookupKeywordFast("where")
lookupKeywordFast("limit")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Sink and validate the lookup results in these benchmarks.

All three benchmarks discard (TokenKind, bool). That makes the numbers easier to skew, and it also lets a broken fast-path/miss path benchmark as if it were a valid keyword hit. Store the results in package-level sinks and fail if ok is false.

Proposed fix
 package lexer
 
 import (
 	"testing"
 )
+
+var (
+	benchmarkKeywordKind TokenKind
+	benchmarkKeywordOK   bool
+)
 
 func BenchmarkLookupKeyword(b *testing.B) {
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		lookupKeyword("select")
-		lookupKeyword("where")
-		lookupKeyword("limit")
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("select")
+		if !benchmarkKeywordOK {
+			b.Fatal("select was not recognized")
+		}
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("where")
+		if !benchmarkKeywordOK {
+			b.Fatal("where was not recognized")
+		}
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("limit")
+		if !benchmarkKeywordOK {
+			b.Fatal("limit was not recognized")
+		}
 	}
 }
 
 func BenchmarkLookupKeywordAlloc(b *testing.B) {
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		lookupKeyword("select")
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("select")
+		if !benchmarkKeywordOK {
+			b.Fatal("select was not recognized")
+		}
 	}
 }
 
 func BenchmarkLookupKeywordFast(b *testing.B) {
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		lookupKeywordFast("select")
-		lookupKeywordFast("where")
-		lookupKeywordFast("limit")
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("select")
+		if !benchmarkKeywordOK {
+			b.Fatal("select was not recognized")
+		}
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("where")
+		if !benchmarkKeywordOK {
+			b.Fatal("where was not recognized")
+		}
+		benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("limit")
+		if !benchmarkKeywordOK {
+			b.Fatal("limit was not recognized")
+		}
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func BenchmarkLookupKeyword(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeyword("select")
lookupKeyword("where")
lookupKeyword("limit")
}
}
func BenchmarkLookupKeywordAlloc(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeyword("select")
}
}
func BenchmarkLookupKeywordFast(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
lookupKeywordFast("select")
lookupKeywordFast("where")
lookupKeywordFast("limit")
}
}
var (
benchmarkKeywordKind TokenKind
benchmarkKeywordOK bool
)
func BenchmarkLookupKeyword(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("select")
if !benchmarkKeywordOK {
b.Fatal("select was not recognized")
}
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("where")
if !benchmarkKeywordOK {
b.Fatal("where was not recognized")
}
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("limit")
if !benchmarkKeywordOK {
b.Fatal("limit was not recognized")
}
}
}
func BenchmarkLookupKeywordAlloc(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeyword("select")
if !benchmarkKeywordOK {
b.Fatal("select was not recognized")
}
}
}
func BenchmarkLookupKeywordFast(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("select")
if !benchmarkKeywordOK {
b.Fatal("select was not recognized")
}
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("where")
if !benchmarkKeywordOK {
b.Fatal("where was not recognized")
}
benchmarkKeywordKind, benchmarkKeywordOK = lookupKeywordFast("limit")
if !benchmarkKeywordOK {
b.Fatal("limit was not recognized")
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/lexer/lookup_test.go` around lines 7 - 31, The benchmarks in
lookup_test.go are discarding the TokenKind and bool results from lookupKeyword
and lookupKeywordFast, which can skew the measurements and hide invalid keyword
misses. Update BenchmarkLookupKeyword, BenchmarkLookupKeywordAlloc, and
BenchmarkLookupKeywordFast to assign the returned values to package-level sink
variables, and add validation that panics or fails the benchmark if ok is false.
Use the existing benchmark functions and the lookupKeyword/lookupKeywordFast
symbols to keep the fix localized.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant