Repository navigation
Conversation
…r small vectors Native query latency regressed (~15% on nytimes-256-angular) relative to the pre-#761 baseline. There were two independent sources, both fixed here. 1. Per-visit cost of lazy MemorySegment creation (introduced by #761) #761 made MemorySegmentVectorFloat(float[]) keep the raw array and defer MemorySegment creation until the first native call, to help vectors that stay in scalar Java code (e.g. the KMeans distance array). But OnDiskGraphIndex.getVector(node) creates a fresh vector per node visited during traversal and hands it straight to NativeVectorUtilSupport cosine/dotProduct, which calls get(). These vectors are ephemeral (one native call, then discarded), so every visit paid a synchronized block, volatile publication and a VarHandle access with nothing to amortize. - MemorySegmentVectorFloat now holds the float[] in a final field and caches its MemorySegment view in a plain field. The view is created lock-free on first get(); racing threads may each build one, which is harmless because heap segments only have final fields and address the same array. The synchronized block, volatile store and VarHandle are gone, and get(int) is a plain array read. - fromVectorFloat/intoVectorFloat in NativeVectorUtilSupport and the MemorySegmentVectorProvider read/write methods use FloatArray.array() directly, so disk I/O no longer materializes a MemorySegment at all. 2. FFI boundary cost on low-dimension vectors For small vectors the cost of crossing the FFI boundary exceeds the gain from the native kernels. NativeVectorUtilSupport now delegates 1-to-1 dotProduct, cosine and squareDistance to the Panama Vector API when the vector length is at or below NATIVE_DIMENSION_THRESHOLD (default 512, override with -Djvector.native_vector_threshold). LazyMemorySegmentTest is updated for the new lazy behavior. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Measures 1-to-1 dotProduct, cosine and squareDistance across vector dimensions for the native and Panama implementations, to help choose the NATIVE_DIMENSION_THRESHOLD crossover point. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
r-devulap
requested review from
MarkWolters,
ashkrisk,
jshook and
tlwillke
as code owners
November 5, 2026 08:43
Contributor
|
Before you submit for review:
If you did not complete any of these, then please explain below. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #782 and #771
Summary
Native query latency and index construction performs worser than Panama by ~15% - 25% on datasets with smaller dimensions (ex:
nytimes-256-angular,sift-128-euclidean, etc.). This PR fixes two independent causes.1. Per-visit cost of lazy
MemorySegmentcreation (from #761)#761 made
MemorySegmentVectorFloat(float[])keep the raw array and create itsMemorySegmenton the first native call. That helps vectors that stay in scalar Java code, butOnDiskGraphIndex.getVector(node)creates a fresh vector per visited node and uses it for one native call. Each visit therefore paid asynchronizedblock, a volatile publication and aVarHandleread, with nothing to spread the cost over.MemorySegmentVectorFloatnow keeps thefloat[]in a final field and caches the segment view in a plain field. The view is created lock-free. Racing threads may each build one, which is harmless because heap segments only have final fields and address the same array.fromVectorFloat/intoVectorFloatand theMemorySegmentVectorProviderread/write paths useFloatArray.array()directly, so disk I/O never materializes aMemorySegment.2. FFI boundary cost on low-dimension vectors
For small vectors the FFI call costs more than the native kernel saves.
NativeVectorUtilSupportnow delegates 1-to-1dotProduct,cosineandsquareDistanceto the Panama Vector API when the vector length is<= NATIVE_DIMENSION_THRESHOLD(default 512, override with-Djvector.native_vector_threshold).Also adds the
NativeThresholdBenchmarkJMH benchmark, which sweeps dimension to find the crossover.Results
Setup: x8i.4xlarge (Intel GNR), full-precision build,
mainvs this branch, mean of 2 reps. Query latency is for an index built with 16 threads.Native mode: mean query latency (ms)
Latency drops 20–34% across every overquery value I checked (1, 2, 5, 10) on all four datasets. Recall@10 is unchanged apart from run-to-run noise of ≤0.01.
Native mode: index build time (s)
Panama mode (control)
Panama-mode runs should not change, and they don't. Query latency moves by −4% to +7% (the +7% is
sift-128-euclideanat overquery 10) and build time by 0.93–1.06×, which is within run-to-run noise for 2 reps.Plots