Skip to content

Conversation

xzfc
Copy link
Contributor

@xzfc xzfc commented Apr 2, 2025

Inside batch getters/scorers, the following approach is common:

// Allocate a temporary array on stack and fill it with default values.
let mut vectors: [&[TElement]; VECTOR_READ_BATCH_SIZE] = [&[]; VECTOR_READ_BATCH_SIZE];

// Fill the array with values.
self.vector_storage
    .get_dense_batch(ids, &mut vectors[..ids.len()]);

Filling temporary array with default values is excessive as these values would be overwritten anyway; but it takes some amount of CPU cycles. If we decide to use batch score methods during search (as suggested in #6245 (comment)), we'll lose a few percents of performance during search on small vectors. To win this performance back, we need to avoid initializing values.

In this PR, these temporary arrays are not initialized, like this:

// Allocate a temporary array on stack, but do not initialize it.
let mut vectors: [&[MaybeUninit<TElement>]; VECTOR_READ_BATCH_SIZE] =
    [MaybeUninit::uninit(); VECTOR_READ_BATCH_SIZE];

// Pass `&[MaybeUninit<T>]`, get `&[T]` pointing to the same data.
let vectors: &[TElement] = self
    .vector_storage
    .get_dense_batch(ids, &mut vectors[..ids.len()]);

MaybeUninit::fill_from would be useful here, but it's not in the stable; so, I've backported a simplified version of it in this PR. An alternative option is to use the uninit::Out::fill_with_iter, but I decided not to pull additional dependency for that.

@xzfc xzfc requested review from timvisee and generall April 2, 2025 13:26
Copy link
Contributor

coderabbitai bot commented Apr 2, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a new public module, maybe_uninit, along with a helper function maybe_uninit_fill_from designed to backport stable behavior for initializing arrays of MaybeUninit types. Multiple method signatures across various vector storage modules have been updated. Specifically, mutable slices of initialized vector references have been replaced with mutable slices of MaybeUninit references, ensuring that the functions only return fully initialized data. The changes extend to both dense and multi-vector storage implementations and update related query scorer methods, where batch processing and memory initialization have been reworked. Certain batch retrieval functions have been removed, and default implementations for batch scoring have been introduced. These updates collectively aim to improve uninitialized memory handling and ensure safety while processing vectors in batch operations.

Possibly related PRs

Suggested reviewers

  • timvisee
  • generall
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
lib/segment/src/vector_storage/in_ram_persisted_vectors.rs (1)

95-99: Method signature improvement for better memory management.

This change enhances performance by removing the need to initialize memory that would immediately be overwritten. By accepting a MaybeUninit slice, the method avoids unnecessary default initialization of references.

Consider adding a brief comment explaining that this function expects the caller to provide an appropriately sized, uninitialized buffer and returns the slice of initialized references.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dce4a94 and f505987.

📒 Files selected for processing (19)
  • lib/common/common/src/lib.rs (1 hunks)
  • lib/common/common/src/maybe_uninit.rs (1 hunks)
  • lib/segment/src/vector_storage/chunked_mmap_vectors.rs (5 hunks)
  • lib/segment/src/vector_storage/chunked_vector_storage.rs (2 hunks)
  • lib/segment/src/vector_storage/dense/appendable_dense_vector_storage.rs (2 hunks)
  • lib/segment/src/vector_storage/dense/memmap_dense_vector_storage.rs (2 hunks)
  • lib/segment/src/vector_storage/dense/mmap_dense_vectors.rs (2 hunks)
  • lib/segment/src/vector_storage/in_ram_persisted_vectors.rs (2 hunks)
  • lib/segment/src/vector_storage/multi_dense/appendable_mmap_multi_dense_vector_storage.rs (0 hunks)
  • lib/segment/src/vector_storage/multi_dense/simple_multi_dense_vector_storage.rs (1 hunks)
  • lib/segment/src/vector_storage/quantized/quantized_custom_query_scorer.rs (0 hunks)
  • lib/segment/src/vector_storage/quantized/quantized_query_scorer.rs (0 hunks)
  • lib/segment/src/vector_storage/query_scorer/custom_query_scorer.rs (2 hunks)
  • lib/segment/src/vector_storage/query_scorer/metric_query_scorer.rs (2 hunks)
  • lib/segment/src/vector_storage/query_scorer/mod.rs (2 hunks)
  • lib/segment/src/vector_storage/query_scorer/multi_custom_query_scorer.rs (2 hunks)
  • lib/segment/src/vector_storage/query_scorer/multi_metric_query_scorer.rs (2 hunks)
  • lib/segment/src/vector_storage/query_scorer/sparse_custom_query_scorer.rs (0 hunks)
  • lib/segment/src/vector_storage/vector_storage_base.rs (4 hunks)
💤 Files with no reviewable changes (4)
  • lib/segment/src/vector_storage/quantized/quantized_query_scorer.rs
  • lib/segment/src/vector_storage/query_scorer/sparse_custom_query_scorer.rs
  • lib/segment/src/vector_storage/multi_dense/appendable_mmap_multi_dense_vector_storage.rs
  • lib/segment/src/vector_storage/quantized/quantized_custom_query_scorer.rs
🧰 Additional context used
🧬 Code Definitions (5)
lib/segment/src/vector_storage/query_scorer/mod.rs (4)
lib/segment/src/vector_storage/query_scorer/custom_query_scorer.rs (1)
  • score_stored_batch (92-106)
lib/segment/src/vector_storage/query_scorer/metric_query_scorer.rs (1)
  • score_stored_batch (73-88)
lib/segment/src/vector_storage/query_scorer/multi_custom_query_scorer.rs (1)
  • score_stored_batch (129-147)
lib/segment/src/vector_storage/query_scorer/multi_metric_query_scorer.rs (1)
  • score_stored_batch (111-129)
lib/segment/src/vector_storage/in_ram_persisted_vectors.rs (2)
lib/segment/src/vector_storage/chunked_mmap_vectors.rs (2)
  • get_batch (299-316)
  • get_batch (405-411)
lib/segment/src/vector_storage/chunked_vector_storage.rs (1)
  • get_batch (51-55)
lib/segment/src/vector_storage/chunked_vector_storage.rs (2)
lib/segment/src/vector_storage/chunked_mmap_vectors.rs (2)
  • get_batch (299-316)
  • get_batch (405-411)
lib/segment/src/vector_storage/in_ram_persisted_vectors.rs (1)
  • get_batch (95-101)
lib/segment/src/vector_storage/dense/appendable_dense_vector_storage.rs (3)
lib/segment/src/vector_storage/vector_storage_base.rs (2)
  • std (141-141)
  • get_dense_batch (132-138)
lib/common/common/src/maybe_uninit.rs (1)
  • maybe_uninit_fill_from (10-33)
lib/segment/src/vector_storage/dense/memmap_dense_vector_storage.rs (1)
  • get_dense_batch (144-151)
lib/segment/src/vector_storage/chunked_mmap_vectors.rs (2)
lib/common/common/src/maybe_uninit.rs (1)
  • maybe_uninit_fill_from (10-33)
lib/segment/src/vector_storage/chunked_vector_storage.rs (2)
  • get_batch (51-55)
  • len (15-15)
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: test-shard-snapshot-api-s3-minio
  • GitHub Check: test-snapshot-operations-s3-minio
  • GitHub Check: test-consistency
  • GitHub Check: test-consensus
  • GitHub Check: test-low-resources
  • GitHub Check: test (macos-latest)
  • GitHub Check: test
  • GitHub Check: test (windows-latest)
  • GitHub Check: test-consensus-compose
  • GitHub Check: test
  • GitHub Check: test
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: Basic TLS/HTTPS tests
🔇 Additional comments (36)
lib/common/common/src/lib.rs (1)

15-15: Looks good: New module for optimizing vector initialization.

The addition of the maybe_uninit module aligns with the PR's objective to optimize performance by eliminating unnecessary vector initialization overhead. This module likely contains the backported version of MaybeUninit::fill_from mentioned in the PR description.

lib/segment/src/vector_storage/query_scorer/custom_query_scorer.rs (2)

3-3: Appropriate import for MaybeUninit.

The import of MaybeUninit is necessary for the optimization changes that follow.


96-99: Performance improvement: Using uninitialized memory.

This implementation now uses MaybeUninit to avoid the overhead of initializing the temporary array with default values before it gets populated with actual data. This matches the PR objective of optimizing initialization of temporary arrays used during vector operations.

lib/segment/src/vector_storage/multi_dense/simple_multi_dense_vector_storage.rs (1)

25-25:

✅ Verification successful

Note the deliberate removal of batch method.

Based on the AI summary, the get_batch_multi method has been completely removed from this implementation. This is part of the broader changes to optimize batch operations by using MaybeUninit elsewhere. The import has been updated to only include what's actually used in the file.


🏁 Script executed:

#!/bin/bash
# Check for any remaining references to get_batch_multi in this file
rg "get_batch_multi" lib/segment/src/vector_storage/multi_dense/simple_multi_dense_vector_storage.rs

Length of output: 100


Removal Verified: The deliberate elimination of get_batch_multi is confirmed

  • The search command produced no matches in lib/segment/src/vector_storage/multi_dense/simple_multi_dense_vector_storage.rs, indicating that get_batch_multi is no longer present.
  • The updated import now correctly includes only CHUNK_SIZE and StoredRecord, aligning with the optimization strategy utilizing MaybeUninit for batch operations.
lib/segment/src/vector_storage/query_scorer/mod.rs (2)

8-8: Necessary import for the default implementation.

Adding the VECTOR_READ_BATCH_SIZE import is necessary for the new default implementation of score_stored_batch.


22-30: Good addition: Default implementation for batch scoring.

Adding a default implementation for score_stored_batch provides a fallback for implementations that don't need specialized batch processing. This is a sensible change that:

  1. Ensures all implementations have this method available
  2. Maintains the batch size constraints with assertions
  3. Provides a simple implementation that calls score_stored for each ID individually

The implementation is correct and aligns with the batch processing pattern seen in the specialized implementors of this trait, while allowing custom optimizations in specific implementations.

lib/segment/src/vector_storage/in_ram_persisted_vectors.rs (1)

1-1: Good addition of necessary import.

The import of MaybeUninit is needed for the changed function signature below.

lib/common/common/src/maybe_uninit.rs (1)

1-33: Well-implemented backport of MaybeUninit::fill_from functionality.

The implementation follows good practices:

  • Clear documentation with usage constraints
  • Properly annotated safety comments
  • Explicit assertion for unsupported types
  • Efficient memory handling

The code correctly:

  1. Fills uninitialized memory with values from an iterator
  2. Tracks the number of initialized elements
  3. Returns both the initialized portion and the remainder

One minor observation:

const { assert!(!std::mem::needs_drop::<I::Item>(), "Not supported") };

This compile-time assertion prevents usage with types that need dropping. This is a reasonable limitation for a simplified backport, and it's well-documented.

lib/segment/src/vector_storage/dense/memmap_dense_vector_storage.rs (2)

4-4: Good addition of necessary import.

The import of MaybeUninit is correctly added for the modified function signature.


144-151: Method signature improvement for better memory management.

This change enhances performance by removing the need to initialize memory that would immediately be overwritten. The method now follows the pattern of accepting uninitialized memory and returning properly initialized values.

The implementation correctly delegates to the mmap_store.get_vectors method with the updated signature.

lib/segment/src/vector_storage/dense/mmap_dense_vectors.rs (3)

3-3: Good addition of necessary import.

The import of MaybeUninit is correctly added for the modified function signatures.


9-9: Good addition of helper function import.

The import of maybe_uninit_fill_from is appropriately added to support the new implementation approach.


160-177: Excellent refactoring to improve memory usage efficiency.

This implementation:

  1. Properly updates the function signature to use MaybeUninit
  2. Uses the new maybe_uninit_fill_from helper function to efficiently populate vectors
  3. Maintains the same logic branches (optimized path for sequential reads)
  4. Returns the properly initialized slice

The refactoring eliminates unnecessary default initialization of memory that would be immediately overwritten, which should improve performance especially for batch operations with small vectors as mentioned in the PR objectives.

I particularly like how the two code paths (with and without prefetching) both use the same pattern, making the code more consistent and easier to understand.

lib/segment/src/vector_storage/query_scorer/metric_query_scorer.rs (2)

3-3: Added MaybeUninit import for optimized vector initialization.


77-81: Improved memory efficiency by using uninitialized memory.

The function now properly uses MaybeUninit to avoid unnecessary initialization of vector values that would immediately be overwritten. This implementation optimizes the performance, especially when working with small vectors, as mentioned in the PR objectives.

lib/segment/src/vector_storage/query_scorer/multi_custom_query_scorer.rs (5)

2-2: Added MaybeUninit import for optimized vector initialization.


130-131: Improved assertions for batch processing.

The assertions have been simplified to directly check ids.len() rather than calculating a separate batch size variable, making the code more direct and easier to understand.


133-136: Optimized memory initialization for batch vector retrieval.

Using MaybeUninit to allocate uninitialized memory for vectors improves performance by eliminating the unnecessary initialization of default values that would be immediately overwritten.


138-138: Improved calculation of total loaded vectors.

The code now directly iterates over the vectors array to calculate the total number of loaded vectors, which is more concise and eliminates the need for slicing.


144-144: Simplified batch processing loop.

The loop now directly uses ids.len() instead of a separate batch size variable, making the code more straightforward.

lib/segment/src/vector_storage/query_scorer/multi_metric_query_scorer.rs (5)

2-2: Added MaybeUninit import for optimized vector initialization.


112-113: Improved assertions for batch processing.

The assertions have been simplified to directly check ids.len() rather than calculating a separate batch size variable, making the code more direct and easier to understand.


115-118: Optimized memory initialization for batch vector retrieval.

Using MaybeUninit to allocate uninitialized memory for vectors improves performance by eliminating the unnecessary initialization of default values that would be immediately overwritten.


120-120: Improved calculation of total read vectors.

The code now directly iterates over the vectors array to calculate the total number of read vectors, which is more concise and eliminates the need for slicing.


126-126: Simplified batch processing loop.

The loop now directly uses ids.len() instead of a separate batch size variable, making the code more straightforward.

lib/segment/src/vector_storage/dense/appendable_dense_vector_storage.rs (2)

3-3: Added MaybeUninit and helper function import.

The code now imports both MaybeUninit for uninitialized memory and the maybe_uninit_fill_from helper function that was introduced in this PR to efficiently work with uninitialized memory.

Also applies to: 10-10


73-85: Optimized batch vector retrieval with MaybeUninit.

The get_dense_batch method has been rewritten to use MaybeUninit for both the input parameter and the internal vector offset handling, which eliminates unnecessary initialization of memory. This implementation is now consistent with the similar code in other storage implementations, as seen in the relevant code snippets from memmap_dense_vector_storage.rs and vector_storage_base.rs.

Key improvements:

  1. Changed parameter type to use MaybeUninit<&'a [T]> instead of initialized references
  2. Uses uninitialized memory for vector offsets
  3. Efficiently fills the offsets using the new maybe_uninit_fill_from helper

This approach aligns with the PR objective of optimizing performance, particularly for operations involving small vectors.

lib/segment/src/vector_storage/chunked_vector_storage.rs (2)

1-1: Good use of MaybeUninit for optimized initialization.

Adding this import supports the new optimized approach for handling uninitialized memory in batch operations.


51-55: Well-designed API change for enhanced performance.

The signature change for get_batch is a good optimization that eliminates unnecessary initialization of vectors. By using MaybeUninit, the method now:

  1. Takes uninitialized memory and populates it directly
  2. Returns a reference to the initialized portion
  3. Avoids the overhead of initializing the array with default values

This aligns perfectly with the PR objective of enhancing performance in batch operations.

lib/segment/src/vector_storage/vector_storage_base.rs (3)

1-1: Added imports support the optimization approach.

The added imports are necessary to support the new optimization pattern using MaybeUninit and the helper function maybe_uninit_fill_from.

Also applies to: 8-8, 29-29


132-138: Default implementation improved for DenseVectorStorage.

The default implementation of get_dense_batch now uses MaybeUninit and maybe_uninit_fill_from to efficiently initialize the vectors array. The new implementation:

  1. Takes uninitialized memory as input
  2. Populates it with vector references
  3. Returns only the initialized portion

This is more efficient than initializing with default values first and offers a better default implementation for implementors of this trait.


154-162: MultiVectorStorage batch implementation optimized.

The get_batch_multi implementation now uses the same optimization pattern with these key improvements:

  1. Changed parameter to use MaybeUninit for uninitialized memory
  2. Added proper debug assertions to validate inputs
  3. Used maybe_uninit_fill_from for safe initialization
  4. Returns the initialized portion

The debug assertions ensure that the vectors and keys arrays have matching lengths and that we don't exceed the batch size limit, which is good for catching errors early in development.

lib/segment/src/vector_storage/chunked_mmap_vectors.rs (4)

3-3: Good addition of necessary imports.

These imports support the new optimization pattern using MaybeUninit and the helper function maybe_uninit_fill_from.

Also applies to: 7-7


299-316: Optimized batch retrieval implementation.

The get_batch implementation has been improved to use MaybeUninit for more efficient memory handling. The key improvements are:

  1. Changed the function signature to use MaybeUninit and return the initialized results
  2. Maintained the same debug assertions to verify input correctness
  3. Used maybe_uninit_fill_from to populate vectors directly without prior initialization
  4. Added proper error handling with panic messages when vectors aren't found

This improves performance by eliminating the unnecessary initialization step while maintaining safety.


405-411: Trait implementation aligned with optimization pattern.

The trait implementation for get_batch has been updated to match the new method signature and properly delegates to the ChunkedMmapVectors::get_batch method. This maintains consistency across the codebase.


467-478: Test code properly updated to use new pattern.

The test code has been updated to:

  1. Use MaybeUninit::uninit() for buffer initialization instead of pre-filled values
  2. Capture the return value from get_batch which now returns the initialized vectors
  3. Maintain the same validation logic to ensure correctness

This validates that the new implementation works correctly while testing the performance optimization.

@xzfc xzfc merged commit 43dc550 into dev Apr 3, 2025
17 checks passed
@xzfc xzfc deleted the maybe-uninit branch April 3, 2025 09:44
pull bot pushed a commit to kp-forks/qdrant that referenced this pull request Apr 21, 2025
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.

2 participants