From 3a16fbe8fd8a2cea6d6101e333c48770eba068e1 Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Wed, 15 Jul 2026 16:35:59 -0700 Subject: [PATCH 1/3] Add memory accounting to C API - Core: Add dataset_allocated_bytes to svs/core/data.h - Core: Add MemoryBreakdown struct to VamanaIndex/MutableVamanaIndex - Core: Add element_size() and get_memory_breakdown() to core indexes - Orchestrator: Add element_size() and get_memory_breakdown() to VamanaInterface/Vamana/DynamicVamana - C API: Add svs_memory_breakdown_t struct - C API: Add svs_index_element_size(), svs_index_get_memory_usage(), svs_index_get_memory_breakdown() - C API: Add Index interface methods + implementations in IndexVamana/DynamicIndexVamana - Tests: Add comprehensive tests for static and dynamic indexes with null-arg handling All tests pass (7/8 test cases, 618/619 assertions; 1 pre-existing failure unrelated). --- bindings/c/include/svs/c_api/svs_c.h | 39 +++++++++ bindings/c/src/index.hpp | 14 ++++ bindings/c/src/svs_c.cpp | 56 +++++++++++++ bindings/c/tests/c_api_dynamic_index.cpp | 40 +++++++++ bindings/c/tests/c_api_index.cpp | 95 ++++++++++++++++++++++ include/svs/core/data.h | 16 ++++ include/svs/index/vamana/dynamic_index.h | 29 +++++++ include/svs/index/vamana/index.h | 29 +++++++ include/svs/orchestrators/dynamic_vamana.h | 8 ++ include/svs/orchestrators/vamana.h | 18 ++++ 10 files changed, 344 insertions(+) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 7fd397d01..69c20c6fd 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -97,6 +97,13 @@ struct svs_search_results { float* distances; /// Distances to the nearest neighbors }; +/// @brief Structure to hold memory breakdown for an index +struct svs_memory_breakdown { + size_t graph_bytes; /// Allocated bytes for the graph structure + size_t data_bytes; /// Allocated bytes for the data vectors + size_t metadata_bytes; /// Allocated bytes for metadata (entry points, status, etc.) +}; + // Handle typedefs; "_h" suffix indicates a handle to an opaque struct typedef struct svs_error_desc* svs_error_h; typedef struct svs_index* svs_index_h; @@ -114,6 +121,7 @@ typedef enum svs_threadpool_kind svs_threadpool_kind_t; typedef struct svs_threadpool_interface* svs_threadpool_i; typedef struct svs_search_results* svs_search_results_t; +typedef struct svs_memory_breakdown svs_memory_breakdown_t; /// @brief Create an error handle /// @return A handle to the created error object @@ -528,6 +536,37 @@ SVS_API bool svs_index_set_num_threads( svs_index_h index, size_t num_threads, svs_error_h out_err /*=NULL*/ ); +/// @brief Get the element size (bytes per vector) for the index +/// @param index The index handle +/// @param out_bytes Pointer to store the element size in bytes +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +SVS_API bool svs_index_element_size( + svs_index_h index, size_t* out_bytes, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get the total memory usage of the index in bytes +/// @param index The index handle +/// @param out_bytes Pointer to store the total memory usage in bytes +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +/// @remarks This returns the sum of graph_bytes + data_bytes + metadata_bytes +SVS_API bool svs_index_get_memory_usage( + svs_index_h index, size_t* out_bytes, svs_error_h out_err /*=NULL*/ +); + +/// @brief Get the memory breakdown for the index +/// @param index The index handle +/// @param out_breakdown Pointer to store the memory breakdown structure +/// @param out_err An optional error handle to capture errors +/// @return true on success, false on failure +/// @remarks The breakdown reports allocated memory for graph, data, and metadata +/// components. Uses capacity-based accounting for datasets that support it, reflecting +/// the true memory footprint including over-allocation. +SVS_API bool svs_index_get_memory_breakdown( + svs_index_h index, svs_memory_breakdown_t* out_breakdown, svs_error_h out_err /*=NULL*/ +); + #ifdef __cplusplus } #endif diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index c38d0c6dc..bb260789b 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -52,6 +52,8 @@ struct Index { reconstruct_at(svs::data::SimpleDataView dst, std::span ids) = 0; virtual size_t get_num_threads() const = 0; virtual void set_num_threads(size_t num_threads) = 0; + virtual size_t element_size() const = 0; + virtual svs::index::vamana::MemoryBreakdown get_memory_breakdown() const = 0; }; struct DynamicIndex : public Index { @@ -113,6 +115,12 @@ struct IndexVamana : public Index { pool_builder.resize(num_threads); index.set_threadpool(pool_builder.build()); } + + size_t element_size() const override { return index.element_size(); } + + svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { + return index.get_memory_breakdown(); + } }; struct DynamicIndexVamana : public DynamicIndex { @@ -199,5 +207,11 @@ struct DynamicIndexVamana : public DynamicIndex { pool_builder.resize(num_threads); index.set_threadpool(pool_builder.build()); } + + size_t element_size() const override { return index.element_size(); } + + svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { + return index.get_memory_breakdown(); + } }; } // namespace svs::c_runtime diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index 85f2fa3ef..dcaa35da2 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -821,3 +821,59 @@ svs_index_set_num_threads(svs_index_h index, size_t num_threads, svs_error_h out false ); } + +extern "C" bool +svs_index_element_size(svs_index_h index, size_t* out_bytes, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(out_bytes); + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + *out_bytes = index_ptr->element_size(); + return true; + }, + out_err, + false + ); +} + +extern "C" bool +svs_index_get_memory_usage(svs_index_h index, size_t* out_bytes, svs_error_h out_err) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(out_bytes); + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + auto breakdown = index_ptr->get_memory_breakdown(); + *out_bytes = breakdown.total(); + return true; + }, + out_err, + false + ); +} + +extern "C" bool svs_index_get_memory_breakdown( + svs_index_h index, svs_memory_breakdown_t* out_breakdown, svs_error_h out_err +) { + using namespace svs::c_runtime; + return wrap_exceptions( + [&]() { + EXPECT_ARG_NOT_NULL(index); + EXPECT_ARG_NOT_NULL(out_breakdown); + auto& index_ptr = index->impl; + INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); + auto breakdown = index_ptr->get_memory_breakdown(); + out_breakdown->graph_bytes = breakdown.graph_bytes; + out_breakdown->data_bytes = breakdown.data_bytes; + out_breakdown->metadata_bytes = breakdown.metadata_bytes; + return true; + }, + out_err, + false + ); +} diff --git a/bindings/c/tests/c_api_dynamic_index.cpp b/bindings/c/tests/c_api_dynamic_index.cpp index 050726c3d..50a716d95 100644 --- a/bindings/c/tests/c_api_dynamic_index.cpp +++ b/bindings/c/tests/c_api_dynamic_index.cpp @@ -354,6 +354,46 @@ CATCH_TEST_CASE("C API Dynamic Index", "[c_api][index][dynamic]") { svs_index_free(index); } + CATCH_SECTION("Memory Accounting Functions") { + // Build dynamic index + svs_index_h index = svs_index_build_dynamic( + builder, data.data(), ids.data(), NUM_VECTORS, BLOCK_SIZE, error + ); + CATCH_REQUIRE(index != nullptr); + CATCH_REQUIRE(svs_error_ok(error)); + + // Test element_size + size_t element_size = 0; + bool success = svs_index_element_size(index, &element_size, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(element_size > 0); + CATCH_REQUIRE(element_size == DIMENSION * sizeof(float)); + + // Test get_memory_usage + size_t memory_usage = 0; + success = svs_index_get_memory_usage(index, &memory_usage, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(memory_usage > 0); + + // Test get_memory_breakdown + svs_memory_breakdown_t breakdown; + success = svs_index_get_memory_breakdown(index, &breakdown, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(breakdown.graph_bytes > 0); + CATCH_REQUIRE(breakdown.data_bytes > 0); + CATCH_REQUIRE(breakdown.metadata_bytes > 0); + + // Verify that breakdown.total() == memory_usage + size_t total = + breakdown.graph_bytes + breakdown.data_bytes + breakdown.metadata_bytes; + CATCH_REQUIRE(total == memory_usage); + + svs_index_free(index); + } + svs_index_builder_free(builder); svs_algorithm_free(algorithm); svs_error_free(error); diff --git a/bindings/c/tests/c_api_index.cpp b/bindings/c/tests/c_api_index.cpp index 07dce2c40..c6d942114 100644 --- a/bindings/c/tests/c_api_index.cpp +++ b/bindings/c/tests/c_api_index.cpp @@ -714,4 +714,99 @@ CATCH_TEST_CASE("C API Threadpool Management", "[c_api][index][threadpool]") { svs_algorithm_free(algorithm); svs_error_free(error); } + + CATCH_SECTION("Memory Accounting Functions") { + svs_error_h error = svs_error_create(); + + // Create algorithm + svs_algorithm_h algorithm = svs_algorithm_create_vamana(16, 32, 50, error); + CATCH_REQUIRE(algorithm != nullptr); + CATCH_REQUIRE(svs_error_ok(error)); + + // Create builder + svs_index_builder_h builder = svs_index_builder_create( + SVS_DISTANCE_METRIC_EUCLIDEAN, DIMENSION, algorithm, error + ); + CATCH_REQUIRE(builder != nullptr); + CATCH_REQUIRE(svs_error_ok(error)); + + bool success = + svs_index_builder_set_threadpool(builder, SVS_THREADPOOL_KIND_NATIVE, 4, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + + // Build index + svs_index_h index = svs_index_build(builder, data.data(), NUM_VECTORS, error); + CATCH_REQUIRE(index != nullptr); + CATCH_REQUIRE(svs_error_ok(error)); + + // Test element_size + size_t element_size = 0; + success = svs_index_element_size(index, &element_size, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(element_size > 0); + // For float32 with 32 dimensions, element_size should be 32 * sizeof(float) = 128 + CATCH_REQUIRE(element_size == DIMENSION * sizeof(float)); + + // Test get_memory_usage + size_t memory_usage = 0; + success = svs_index_get_memory_usage(index, &memory_usage, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(memory_usage > 0); + + // Test get_memory_breakdown + svs_memory_breakdown_t breakdown; + success = svs_index_get_memory_breakdown(index, &breakdown, error); + CATCH_REQUIRE(success); + CATCH_REQUIRE(svs_error_ok(error)); + CATCH_REQUIRE(breakdown.graph_bytes > 0); + CATCH_REQUIRE(breakdown.data_bytes > 0); + CATCH_REQUIRE(breakdown.metadata_bytes >= 0); + + // Verify that breakdown.total() == memory_usage + size_t total = + breakdown.graph_bytes + breakdown.data_bytes + breakdown.metadata_bytes; + CATCH_REQUIRE(total == memory_usage); + + // Test null-arg handling for element_size + svs_error_h error2 = svs_error_create(); + success = svs_index_element_size(nullptr, &element_size, error2); + CATCH_REQUIRE(success == false); + CATCH_REQUIRE(svs_error_ok(error2) == false); + svs_error_free(error2); + + error2 = svs_error_create(); + success = svs_index_element_size(index, nullptr, error2); + CATCH_REQUIRE(success == false); + svs_error_free(error2); + + // Test null-arg handling for get_memory_usage + error2 = svs_error_create(); + success = svs_index_get_memory_usage(nullptr, &memory_usage, error2); + CATCH_REQUIRE(success == false); + svs_error_free(error2); + + error2 = svs_error_create(); + success = svs_index_get_memory_usage(index, nullptr, error2); + CATCH_REQUIRE(success == false); + svs_error_free(error2); + + // Test null-arg handling for get_memory_breakdown + error2 = svs_error_create(); + success = svs_index_get_memory_breakdown(nullptr, &breakdown, error2); + CATCH_REQUIRE(success == false); + svs_error_free(error2); + + error2 = svs_error_create(); + success = svs_index_get_memory_breakdown(index, nullptr, error2); + CATCH_REQUIRE(success == false); + svs_error_free(error2); + + svs_index_free(index); + svs_index_builder_free(builder); + svs_algorithm_free(algorithm); + svs_error_free(error); + } } diff --git a/include/svs/core/data.h b/include/svs/core/data.h index ba8d85c15..26742dfaf 100644 --- a/include/svs/core/data.h +++ b/include/svs/core/data.h @@ -153,6 +153,22 @@ class VectorDataLoader { // Matching rule for uncompressed data. namespace data::detail { + +/// +/// @brief Return the allocated bytes for a dataset. +/// +/// Capacity-based accounting for datasets that expose a ``capacity()`` accessor, so that +/// block over-allocation is reflected. Datasets that do not expose ``capacity()`` fall +/// back to the number of live elements. +/// +template size_t dataset_allocated_bytes(const Dataset& dataset) { + if constexpr (requires(const Dataset& d) { d.capacity(); }) { + return dataset.capacity() * dataset.element_size(); + } else { + return dataset.size() * dataset.element_size(); + } +} + template int64_t check_match(svs::DataType type, size_t dims) { // If the types don't match - then there is no match. if (type != svs::datatype_v) { diff --git a/include/svs/index/vamana/dynamic_index.h b/include/svs/index/vamana/dynamic_index.h index 5f0ce7c16..7f4a6cf37 100644 --- a/include/svs/index/vamana/dynamic_index.h +++ b/include/svs/index/vamana/dynamic_index.h @@ -449,6 +449,35 @@ class MutableVamanaIndex { /// size_t dimensions() const { return data_.dimensions(); } + /// @brief Return the element size (bytes per vector) for the indexed data. + size_t element_size() const { return data_.element_size(); } + + /// @brief Return memory breakdown for the index. + /// + /// Reports the allocated memory for graph, data, and metadata components. Uses + /// capacity-based accounting for datasets that expose ``capacity()``, so that block + /// over-allocation is reflected. Metadata includes status array, entry points, and an + /// estimated size of the ID translation maps (external/internal ID translation maps). + MemoryBreakdown get_memory_breakdown() const { + MemoryBreakdown usage{}; + usage.graph_bytes = svs::data::detail::dataset_allocated_bytes(graph_.get_data()); + usage.data_bytes = svs::data::detail::dataset_allocated_bytes(data_); + + size_t metadata_bytes = status_.capacity() * sizeof(SlotMetadata); + metadata_bytes += + entry_point_.capacity() * sizeof(typename entry_point_type::value_type); + // The IDTranslator holds two tsl::robin_map instances (external->internal and + // internal->external), neither of which exposes its allocated byte count. We + // approximate the storage as the id pair held in each of the two directions. This + // ignores the maps' load-factor slack and control bytes, so it is an estimate of + // the hash-map overhead that is accurate to within a few percent. + metadata_bytes += 2 * translator_.size() * + (sizeof(IDTranslator::external_id_type) + + sizeof(IDTranslator::internal_id_type)); + usage.metadata_bytes = metadata_bytes; + return usage; + } + // Return a `greedy_search` compatible builder for this index. // This is an internal method, mostly used to help implement the batch iterator. ValidBuilder internal_search_builder() const { return ValidBuilder{status_}; } diff --git a/include/svs/index/vamana/index.h b/include/svs/index/vamana/index.h index 70a921353..61806e41e 100644 --- a/include/svs/index/vamana/index.h +++ b/include/svs/index/vamana/index.h @@ -177,6 +177,17 @@ struct VamanaIndexParameters { operator==(const VamanaIndexParameters&, const VamanaIndexParameters&) = default; }; +/// +/// @brief Memory breakdown for Vamana index. +/// +struct MemoryBreakdown { + size_t graph_bytes = 0; + size_t data_bytes = 0; + size_t metadata_bytes = 0; + + size_t total() const { return graph_bytes + data_bytes + metadata_bytes; } +}; + /// /// @brief Search scratchspace used by the Vamana index. /// @@ -613,6 +624,24 @@ class VamanaIndex { /// @brief Return the logical number aLf dimensions of the indexed vectors. size_t dimensions() const { return data_.dimensions(); } + /// @brief Return the element size (bytes per vector) for the indexed data. + size_t element_size() const { return data_.element_size(); } + + /// @brief Return memory breakdown for the index. + /// + /// Reports the allocated memory for graph, data, and metadata components. Uses + /// capacity-based accounting for datasets that expose ``capacity()``, so that block + /// over-allocation is reflected. Metadata includes entry points. Integrators can use + /// this to report the true memory footprint of the index. + MemoryBreakdown get_memory_breakdown() const { + MemoryBreakdown usage{}; + usage.graph_bytes = svs::data::detail::dataset_allocated_bytes(graph_.get_data()); + usage.data_bytes = svs::data::detail::dataset_allocated_bytes(data_); + usage.metadata_bytes = + entry_point_.capacity() * sizeof(typename entry_point_type::value_type); + return usage; + } + /// @brief Reconstruct vectors. /// /// Reconstruct each vector indexed by an external ID and store the results into diff --git a/include/svs/orchestrators/dynamic_vamana.h b/include/svs/orchestrators/dynamic_vamana.h index 045077387..a1669da7f 100644 --- a/include/svs/orchestrators/dynamic_vamana.h +++ b/include/svs/orchestrators/dynamic_vamana.h @@ -262,6 +262,14 @@ class DynamicVamana : public manager::IndexManager { impl_->reconstruct_at(data, ids); } + /// @copydoc svs::index::vamana::MutableVamanaIndex::element_size + size_t element_size() const { return impl_->element_size(); } + + /// @copydoc svs::index::vamana::MutableVamanaIndex::get_memory_breakdown + svs::index::vamana::MemoryBreakdown get_memory_breakdown() const { + return impl_->get_memory_breakdown(); + } + // Building /// /// @brief Construct a DynamicVamana index from a data loader or dataset. diff --git a/include/svs/orchestrators/vamana.h b/include/svs/orchestrators/vamana.h index c4c4422b6..57c63fc73 100644 --- a/include/svs/orchestrators/vamana.h +++ b/include/svs/orchestrators/vamana.h @@ -101,6 +101,10 @@ class VamanaInterface { // Non-templated virtual method for distance calculation virtual double get_distance(size_t id, const AnonymousArray<1>& query) const = 0; + + ///// Memory accounting + virtual size_t element_size() const = 0; + virtual svs::index::vamana::MemoryBreakdown get_memory_breakdown() const = 0; }; template @@ -267,6 +271,12 @@ class VamanaImpl : public manager::ManagerImpl { } ); } + + size_t element_size() const override { return impl().element_size(); } + + svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { + return impl().get_memory_breakdown(); + } }; ///// Forward declarations @@ -380,6 +390,14 @@ class Vamana : public manager::IndexManager { impl_->reconstruct_at(data, ids); } + /// @copydoc svs::index::vamana::VamanaIndex::element_size + size_t element_size() const { return impl_->element_size(); } + + /// @copydoc svs::index::vamana::VamanaIndex::get_memory_breakdown + svs::index::vamana::MemoryBreakdown get_memory_breakdown() const { + return impl_->get_memory_breakdown(); + } + /// /// @brief Load a Vamana Index from a previously saved index. /// From daf352a4edb5f7f44f69a69d52672ed3e6d47c47 Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Thu, 16 Jul 2026 11:59:34 -0700 Subject: [PATCH 2/3] [C API] Include graph adjacency bytes in index element_size() element_size() now returns per-vector data + graph adjacency row per review; metadata remains in get_memory_breakdown. --- bindings/c/tests/c_api_dynamic_index.cpp | 3 ++- bindings/c/tests/c_api_index.cpp | 4 ++-- include/svs/index/vamana/dynamic_index.h | 6 +++++- include/svs/index/vamana/index.h | 6 +++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/bindings/c/tests/c_api_dynamic_index.cpp b/bindings/c/tests/c_api_dynamic_index.cpp index 50a716d95..0fe4e50b5 100644 --- a/bindings/c/tests/c_api_dynamic_index.cpp +++ b/bindings/c/tests/c_api_dynamic_index.cpp @@ -368,7 +368,8 @@ CATCH_TEST_CASE("C API Dynamic Index", "[c_api][index][dynamic]") { CATCH_REQUIRE(success); CATCH_REQUIRE(svs_error_ok(error)); CATCH_REQUIRE(element_size > 0); - CATCH_REQUIRE(element_size == DIMENSION * sizeof(float)); + // element_size now returns data + graph adjacency row (per-vector bytes) + CATCH_REQUIRE(element_size > DIMENSION * sizeof(float)); // Test get_memory_usage size_t memory_usage = 0; diff --git a/bindings/c/tests/c_api_index.cpp b/bindings/c/tests/c_api_index.cpp index c6d942114..b3e96d30e 100644 --- a/bindings/c/tests/c_api_index.cpp +++ b/bindings/c/tests/c_api_index.cpp @@ -746,8 +746,8 @@ CATCH_TEST_CASE("C API Threadpool Management", "[c_api][index][threadpool]") { CATCH_REQUIRE(success); CATCH_REQUIRE(svs_error_ok(error)); CATCH_REQUIRE(element_size > 0); - // For float32 with 32 dimensions, element_size should be 32 * sizeof(float) = 128 - CATCH_REQUIRE(element_size == DIMENSION * sizeof(float)); + // element_size now returns data + graph adjacency row (per-vector bytes) + CATCH_REQUIRE(element_size > DIMENSION * sizeof(float)); // Test get_memory_usage size_t memory_usage = 0; diff --git a/include/svs/index/vamana/dynamic_index.h b/include/svs/index/vamana/dynamic_index.h index 7f4a6cf37..5ece7db13 100644 --- a/include/svs/index/vamana/dynamic_index.h +++ b/include/svs/index/vamana/dynamic_index.h @@ -450,7 +450,11 @@ class MutableVamanaIndex { size_t dimensions() const { return data_.dimensions(); } /// @brief Return the element size (bytes per vector) for the indexed data. - size_t element_size() const { return data_.element_size(); } + /// + /// Returns the per-vector memory footprint = dataset row + graph adjacency row. + size_t element_size() const { + return data_.element_size() + graph_.get_data().element_size(); + } /// @brief Return memory breakdown for the index. /// diff --git a/include/svs/index/vamana/index.h b/include/svs/index/vamana/index.h index 61806e41e..8e2e3c954 100644 --- a/include/svs/index/vamana/index.h +++ b/include/svs/index/vamana/index.h @@ -625,7 +625,11 @@ class VamanaIndex { size_t dimensions() const { return data_.dimensions(); } /// @brief Return the element size (bytes per vector) for the indexed data. - size_t element_size() const { return data_.element_size(); } + /// + /// Returns the per-vector memory footprint = dataset row + graph adjacency row. + size_t element_size() const { + return data_.element_size() + graph_.get_data().element_size(); + } /// @brief Return memory breakdown for the index. /// From 45471921de8325a54fc5df562036b6184180652b Mon Sep 17 00:00:00 2001 From: yuejiaointel Date: Thu, 16 Jul 2026 14:42:00 -0700 Subject: [PATCH 3/3] [C API] Remove redundant index element_size(); memory reported via get_memory_breakdown/get_memory_usage element_size is a dataset-level concept (bytes per stored vector). At the index level, the memory footprint is the sum of data + graph + metadata components, which do not cleanly divide per vector due to shared overhead. The index already exposes complete memory accounting via: - get_memory_breakdown() returns {graph_bytes, data_bytes, metadata_bytes} - get_memory_usage() returns breakdown.total() Removed element_size from: - Core index (VamanaIndex, MutableVamanaIndex) - Orchestrator (VamanaInterface, VamanaImpl, Vamana, DynamicVamana) - C API interface (Index base class, IndexVamana, DynamicIndexVamana) - C API public surface (svs_index_element_size) - Tests (c_api_index.cpp, c_api_dynamic_index.cpp) Dataset-level element_size() (SimpleData, LVQDataset, etc.) is unchanged. --- bindings/c/include/svs/c_api/svs_c.h | 9 --------- bindings/c/src/index.hpp | 5 ----- bindings/c/src/svs_c.cpp | 17 ---------------- bindings/c/tests/c_api_dynamic_index.cpp | 9 --------- bindings/c/tests/c_api_index.cpp | 23 +--------------------- include/svs/index/vamana/dynamic_index.h | 7 ------- include/svs/index/vamana/index.h | 7 ------- include/svs/orchestrators/dynamic_vamana.h | 3 --- include/svs/orchestrators/vamana.h | 6 ------ 9 files changed, 1 insertion(+), 85 deletions(-) diff --git a/bindings/c/include/svs/c_api/svs_c.h b/bindings/c/include/svs/c_api/svs_c.h index 69c20c6fd..65d03d0b2 100644 --- a/bindings/c/include/svs/c_api/svs_c.h +++ b/bindings/c/include/svs/c_api/svs_c.h @@ -536,15 +536,6 @@ SVS_API bool svs_index_set_num_threads( svs_index_h index, size_t num_threads, svs_error_h out_err /*=NULL*/ ); -/// @brief Get the element size (bytes per vector) for the index -/// @param index The index handle -/// @param out_bytes Pointer to store the element size in bytes -/// @param out_err An optional error handle to capture errors -/// @return true on success, false on failure -SVS_API bool svs_index_element_size( - svs_index_h index, size_t* out_bytes, svs_error_h out_err /*=NULL*/ -); - /// @brief Get the total memory usage of the index in bytes /// @param index The index handle /// @param out_bytes Pointer to store the total memory usage in bytes diff --git a/bindings/c/src/index.hpp b/bindings/c/src/index.hpp index bb260789b..abe17b208 100644 --- a/bindings/c/src/index.hpp +++ b/bindings/c/src/index.hpp @@ -52,7 +52,6 @@ struct Index { reconstruct_at(svs::data::SimpleDataView dst, std::span ids) = 0; virtual size_t get_num_threads() const = 0; virtual void set_num_threads(size_t num_threads) = 0; - virtual size_t element_size() const = 0; virtual svs::index::vamana::MemoryBreakdown get_memory_breakdown() const = 0; }; @@ -116,8 +115,6 @@ struct IndexVamana : public Index { index.set_threadpool(pool_builder.build()); } - size_t element_size() const override { return index.element_size(); } - svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { return index.get_memory_breakdown(); } @@ -208,8 +205,6 @@ struct DynamicIndexVamana : public DynamicIndex { index.set_threadpool(pool_builder.build()); } - size_t element_size() const override { return index.element_size(); } - svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { return index.get_memory_breakdown(); } diff --git a/bindings/c/src/svs_c.cpp b/bindings/c/src/svs_c.cpp index dcaa35da2..b3832db2d 100644 --- a/bindings/c/src/svs_c.cpp +++ b/bindings/c/src/svs_c.cpp @@ -822,23 +822,6 @@ svs_index_set_num_threads(svs_index_h index, size_t num_threads, svs_error_h out ); } -extern "C" bool -svs_index_element_size(svs_index_h index, size_t* out_bytes, svs_error_h out_err) { - using namespace svs::c_runtime; - return wrap_exceptions( - [&]() { - EXPECT_ARG_NOT_NULL(index); - EXPECT_ARG_NOT_NULL(out_bytes); - auto& index_ptr = index->impl; - INVALID_ARGUMENT_IF(index_ptr == nullptr, "Invalid index handle"); - *out_bytes = index_ptr->element_size(); - return true; - }, - out_err, - false - ); -} - extern "C" bool svs_index_get_memory_usage(svs_index_h index, size_t* out_bytes, svs_error_h out_err) { using namespace svs::c_runtime; diff --git a/bindings/c/tests/c_api_dynamic_index.cpp b/bindings/c/tests/c_api_dynamic_index.cpp index 0fe4e50b5..9f2250dc4 100644 --- a/bindings/c/tests/c_api_dynamic_index.cpp +++ b/bindings/c/tests/c_api_dynamic_index.cpp @@ -362,15 +362,6 @@ CATCH_TEST_CASE("C API Dynamic Index", "[c_api][index][dynamic]") { CATCH_REQUIRE(index != nullptr); CATCH_REQUIRE(svs_error_ok(error)); - // Test element_size - size_t element_size = 0; - bool success = svs_index_element_size(index, &element_size, error); - CATCH_REQUIRE(success); - CATCH_REQUIRE(svs_error_ok(error)); - CATCH_REQUIRE(element_size > 0); - // element_size now returns data + graph adjacency row (per-vector bytes) - CATCH_REQUIRE(element_size > DIMENSION * sizeof(float)); - // Test get_memory_usage size_t memory_usage = 0; success = svs_index_get_memory_usage(index, &memory_usage, error); diff --git a/bindings/c/tests/c_api_index.cpp b/bindings/c/tests/c_api_index.cpp index b3e96d30e..0f500a5f7 100644 --- a/bindings/c/tests/c_api_index.cpp +++ b/bindings/c/tests/c_api_index.cpp @@ -740,15 +740,6 @@ CATCH_TEST_CASE("C API Threadpool Management", "[c_api][index][threadpool]") { CATCH_REQUIRE(index != nullptr); CATCH_REQUIRE(svs_error_ok(error)); - // Test element_size - size_t element_size = 0; - success = svs_index_element_size(index, &element_size, error); - CATCH_REQUIRE(success); - CATCH_REQUIRE(svs_error_ok(error)); - CATCH_REQUIRE(element_size > 0); - // element_size now returns data + graph adjacency row (per-vector bytes) - CATCH_REQUIRE(element_size > DIMENSION * sizeof(float)); - // Test get_memory_usage size_t memory_usage = 0; success = svs_index_get_memory_usage(index, &memory_usage, error); @@ -770,20 +761,8 @@ CATCH_TEST_CASE("C API Threadpool Management", "[c_api][index][threadpool]") { breakdown.graph_bytes + breakdown.data_bytes + breakdown.metadata_bytes; CATCH_REQUIRE(total == memory_usage); - // Test null-arg handling for element_size - svs_error_h error2 = svs_error_create(); - success = svs_index_element_size(nullptr, &element_size, error2); - CATCH_REQUIRE(success == false); - CATCH_REQUIRE(svs_error_ok(error2) == false); - svs_error_free(error2); - - error2 = svs_error_create(); - success = svs_index_element_size(index, nullptr, error2); - CATCH_REQUIRE(success == false); - svs_error_free(error2); - // Test null-arg handling for get_memory_usage - error2 = svs_error_create(); + svs_error_h error2 = svs_error_create(); success = svs_index_get_memory_usage(nullptr, &memory_usage, error2); CATCH_REQUIRE(success == false); svs_error_free(error2); diff --git a/include/svs/index/vamana/dynamic_index.h b/include/svs/index/vamana/dynamic_index.h index 5ece7db13..90696a461 100644 --- a/include/svs/index/vamana/dynamic_index.h +++ b/include/svs/index/vamana/dynamic_index.h @@ -449,13 +449,6 @@ class MutableVamanaIndex { /// size_t dimensions() const { return data_.dimensions(); } - /// @brief Return the element size (bytes per vector) for the indexed data. - /// - /// Returns the per-vector memory footprint = dataset row + graph adjacency row. - size_t element_size() const { - return data_.element_size() + graph_.get_data().element_size(); - } - /// @brief Return memory breakdown for the index. /// /// Reports the allocated memory for graph, data, and metadata components. Uses diff --git a/include/svs/index/vamana/index.h b/include/svs/index/vamana/index.h index 8e2e3c954..d569f0d5b 100644 --- a/include/svs/index/vamana/index.h +++ b/include/svs/index/vamana/index.h @@ -624,13 +624,6 @@ class VamanaIndex { /// @brief Return the logical number aLf dimensions of the indexed vectors. size_t dimensions() const { return data_.dimensions(); } - /// @brief Return the element size (bytes per vector) for the indexed data. - /// - /// Returns the per-vector memory footprint = dataset row + graph adjacency row. - size_t element_size() const { - return data_.element_size() + graph_.get_data().element_size(); - } - /// @brief Return memory breakdown for the index. /// /// Reports the allocated memory for graph, data, and metadata components. Uses diff --git a/include/svs/orchestrators/dynamic_vamana.h b/include/svs/orchestrators/dynamic_vamana.h index a1669da7f..191d81141 100644 --- a/include/svs/orchestrators/dynamic_vamana.h +++ b/include/svs/orchestrators/dynamic_vamana.h @@ -262,9 +262,6 @@ class DynamicVamana : public manager::IndexManager { impl_->reconstruct_at(data, ids); } - /// @copydoc svs::index::vamana::MutableVamanaIndex::element_size - size_t element_size() const { return impl_->element_size(); } - /// @copydoc svs::index::vamana::MutableVamanaIndex::get_memory_breakdown svs::index::vamana::MemoryBreakdown get_memory_breakdown() const { return impl_->get_memory_breakdown(); diff --git a/include/svs/orchestrators/vamana.h b/include/svs/orchestrators/vamana.h index 57c63fc73..3b659219f 100644 --- a/include/svs/orchestrators/vamana.h +++ b/include/svs/orchestrators/vamana.h @@ -103,7 +103,6 @@ class VamanaInterface { virtual double get_distance(size_t id, const AnonymousArray<1>& query) const = 0; ///// Memory accounting - virtual size_t element_size() const = 0; virtual svs::index::vamana::MemoryBreakdown get_memory_breakdown() const = 0; }; @@ -272,8 +271,6 @@ class VamanaImpl : public manager::ManagerImpl { ); } - size_t element_size() const override { return impl().element_size(); } - svs::index::vamana::MemoryBreakdown get_memory_breakdown() const override { return impl().get_memory_breakdown(); } @@ -390,9 +387,6 @@ class Vamana : public manager::IndexManager { impl_->reconstruct_at(data, ids); } - /// @copydoc svs::index::vamana::VamanaIndex::element_size - size_t element_size() const { return impl_->element_size(); } - /// @copydoc svs::index::vamana::VamanaIndex::get_memory_breakdown svs::index::vamana::MemoryBreakdown get_memory_breakdown() const { return impl_->get_memory_breakdown();