Skip to content

perf: optimize LID comparison in iterators#358

Open
cheb0 wants to merge 10 commits intomainfrom
0-fast-cmp-xor
Open

perf: optimize LID comparison in iterators#358
cheb0 wants to merge 10 commits intomainfrom
0-fast-cmp-xor

Conversation

@cheb0
Copy link
Member

@cheb0 cheb0 commented Feb 17, 2026

Description

Adds a zero-cost comparison for LIDs in iterators, unblocks NextGEQ (need fast comparisons and min function for NextGEQ to be effective)

Details

Instead of calling the n.less comparator, we introduce a new type which hides all comparison logic. It's a struct node.LID (name can be changed).

Struct size is 64 bits. For default order (desc) we keep two fields:
lid: LID (32 bits), XOR mask: 0 (32 bits)
For reverse (order asc) we keep:
lid: LID ^ 0xFFFFFFFFF (32 bits), mask: 0xFFFFFFFFF (32 bits)
To unpack a LID we just do lid ^ mask.

This makes masked LID value always go in ascending way (both orders). i.e. for default order (docs sort desc) LIDs remain unchanged, but for reverse odder (docs sort asc) higher LID values are transformed to lower values in lid field in LID struct. Therefore, nodes no longer need to maintain reverse flag and execute same code for both orders. We can also effieciently compare values via ordinary <. We can also support efficient min/max function.

Leaf nodes (IteratorDesc/Asc, staticAsc/Desc) return terminal values. For default order we return the last LID as MaxUint32, for reverse order it's 0. Inversion makes both values equal (first 32 bits), we can always tell if it's null value just by comparing with MaxUint32.

Because null values are represented as MaxUint32 masked lid, we can eliminate unneeded if checks. Previously we had:
n.hasLeft && n.less(n.leftID, n.rightID)
Now we can just:
leftID.Less(rightID)
Because null values are represented as MaxUint32, this if check will never pass if leftID is null.

The encoded LID value is yielded directly from eval tree (search) and passed right to agg tree, where it can also be effectively compared to different values.

Measurements

Fractions taken from prod:

service:large_service, count by k8_pod (hot, data cached)
master: ~470 ms
fast cmp: ~420 ms

service:small_service, count by k8_pod (hot, data cached)
master: ~400 ms
fast cmp: ~330ms


  • I have read and followed all requirements in CONTRIBUTING.md;
  • I used LLM/AI assistance to make this pull request;

@cheb0
Copy link
Member Author

cheb0 commented Feb 17, 2026

@seqbenchbot up main search-logbench

@github-actions
Copy link
Contributor

🔴 Performance Degradation

Some benchmarks have degraded compared to the previous run.
Click on Show table button to see full list of degraded benchmarks.

Show table
Name Previous Current Ratio Verdict
FindSequence_Random/small-4 b369fc 05d8ea
4974.22 MB/s 4303.93 MB/s 0.87 🔴

@cheb0 cheb0 changed the title perf: fast lid representation for optimized comparison in iterators perf: new lid representation for optimized comparison in iterators Feb 17, 2026
@cheb0 cheb0 marked this pull request as draft February 17, 2026 13:26
@ozontech ozontech deleted a comment from seqbenchbot Feb 18, 2026
@ozontech ozontech deleted a comment from seqbenchbot Feb 18, 2026
@cheb0 cheb0 changed the title perf: new lid representation for optimized comparison in iterators perf: new lid representation for efficient comparison in iterators Feb 18, 2026
@cheb0 cheb0 marked this pull request as ready for review February 18, 2026 07:02
@codecov-commenter
Copy link

codecov-commenter commented Feb 18, 2026

Codecov Report

❌ Patch coverage is 96.15385% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.44%. Comparing base (158eee6) to head (8c12ab3).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
node/lid.go 84.00% 4 Missing ⚠️
frac/processor/search.go 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #358      +/-   ##
==========================================
- Coverage   71.51%   71.44%   -0.08%     
==========================================
  Files         205      205              
  Lines       14872    14921      +49     
==========================================
+ Hits        10636    10660      +24     
- Misses       3468     3486      +18     
- Partials      768      775       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cheb0 cheb0 changed the title perf: new lid representation for efficient comparison in iterators perf: implement new LID representation for efficient comparison in iterators Feb 18, 2026
@cheb0 cheb0 changed the title perf: implement new LID representation for efficient comparison in iterators perf: optimize LID comparison in iterators Feb 20, 2026
@cheb0 cheb0 mentioned this pull request Feb 20, 2026
2 tasks
@ozontech ozontech deleted a comment from github-actions bot Mar 2, 2026
@ozontech ozontech deleted a comment from github-actions bot Mar 2, 2026
@ozontech ozontech deleted a comment from github-actions bot Mar 2, 2026
@ozontech ozontech deleted a comment from github-actions bot Mar 2, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 2, 2026

🔴 Performance Degradation

Some benchmarks have degraded compared to the previous run.
Click on Show table button to see full list of degraded benchmarks.

Show table
Name Previous Current Ratio Verdict
AggDeep/size=1000-4 8aefcc 16a906
4817.00 ns/op 5463.00 ns/op 1.13 🔴
AggDeep/size=1000000-4 8aefcc 16a906
4837044.00 ns/op 5560985.00 ns/op 1.15 🔴
AggWide/size=10000-4 8aefcc 16a906
47761.00 ns/op 54451.00 ns/op 1.14 🔴
AggWide/size=1000000-4 8aefcc 16a906
516.00 B/op 580.00 B/op 1.12 🔴
5419714.00 ns/op 6230424.00 ns/op 1.15 🔴
FindSequence_Random/medium-4 8aefcc 16a906
10747.09 MB/s 9189.63 MB/s 0.86 🔴
99.33 ns/op 111.40 ns/op 1.12 🔴
FindSequence_Random/small-4 8aefcc 16a906
6638.44 MB/s 4563.10 MB/s 0.69 🔴
46.45 ns/op 56.10 ns/op 1.21 🔴

@ozontech ozontech deleted a comment from github-actions bot Mar 2, 2026
@eguguchkin eguguchkin added this to the v0.69.0 milestone Mar 2, 2026
@eguguchkin eguguchkin requested review from dkharms, eguguchkin and forshev and removed request for dkharms March 2, 2026 10:29
withAggQuery(processor.AggQuery{GroupBy: aggField("level")})),
[]map[string]uint64{
{"gateway": 3, "proxy": 2, "scheduler": 1},
{gateway: 3, proxy: 2, "scheduler": 1},
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use const scheduler here as well

}

func (c LID) Eq(other LID) bool {
return c.lid == other.lid
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: is it possible scenario when user of this method compares asc lid and desc lid? at first glance looks like no. however, do we need to take order into account here and check mask?

for example

descLid := NewLIDOrderDesc(5)
ascLid := NewLIDOrderAsc(math.MaxUint32 - 5)
descLid.Eq(ascLid) // produces true now

}

// Less compares two values. It also does an implicit null check, since we store math.MaxUint32 for null values.
// Which means if we call x.Less(y), then we now for sure that x is not null. Therefore, this Less call can work
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: then we know for sure?

n.readLeft()
}
for n.hasLeft && n.hasRight && n.less(n.rightID, n.leftID) {
for !n.rightID.IsNull() && n.rightID.Less(n.leftID) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should it be !n.leftID.IsNull() && n.rightID.Less(n.leftID) so we check both ids for null?

// For reverse order LID is inverted as follows: "MaxUint32 - LID" formula using XOR mask. Terminal LID value is 0 instead
// of MaxUint32 in reverse order, but 0 is XORed to MaxUint32. Which means, null value will always have lid field set to
// 0xFFFFFFFF (math.MaxUint32) regardless of reverse (order) flag.
type LID struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: maybe ComparableLID? a bit longer but gives more context imo

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.

4 participants