From 8027a4dca6623eeff910ecedf577a62c9c9da437 Mon Sep 17 00:00:00 2001 From: meiravgri Date: Mon, 8 Sep 2025 11:33:29 +0000 Subject: [PATCH 1/3] initial backport --- tests/benchmark/bm_common.h | 15 +-- tests/benchmark/bm_vecsim_basics.h | 93 ++++++++++++++++++- .../run_files/bm_basics_multi_fp32.cpp | 6 ++ .../run_files/bm_basics_single_fp32.cpp | 7 ++ 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/tests/benchmark/bm_common.h b/tests/benchmark/bm_common.h index 8653d401b..4ff8abe62 100644 --- a/tests/benchmark/bm_common.h +++ b/tests/benchmark/bm_common.h @@ -60,8 +60,9 @@ void BM_VecSimCommon::Memory_FLAT(benchmark::State &st, unsigned s for (auto _ : st) { // Do nothing... } - st.counters["memory"] = - (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_BF + index_offset]).memory; + st.counters["memory"] = benchmark::Counter( + (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_BF + index_offset]).memory, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); } template void BM_VecSimCommon::Memory_HNSW(benchmark::State &st, unsigned short index_offset) { @@ -69,8 +70,9 @@ void BM_VecSimCommon::Memory_HNSW(benchmark::State &st, unsigned s for (auto _ : st) { // Do nothing... } - st.counters["memory"] = - (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_HNSWLIB + index_offset]).memory; + st.counters["memory"] = benchmark::Counter( + (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_HNSWLIB + index_offset]).memory, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); } template void BM_VecSimCommon::Memory_Tiered(benchmark::State &st, @@ -79,8 +81,9 @@ void BM_VecSimCommon::Memory_Tiered(benchmark::State &st, for (auto _ : st) { // Do nothing... } - st.counters["memory"] = - (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_TIERED + index_offset]).memory; + st.counters["memory"] = benchmark::Counter( + (double)VecSimIndex_StatsInfo(INDICES[VecSimAlgo_TIERED + index_offset]).memory, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); } // TopK search BM diff --git a/tests/benchmark/bm_vecsim_basics.h b/tests/benchmark/bm_vecsim_basics.h index 0ddfb240d..7a6db8062 100644 --- a/tests/benchmark/bm_vecsim_basics.h +++ b/tests/benchmark/bm_vecsim_basics.h @@ -31,6 +31,14 @@ class BM_VecSimBasics : public BM_VecSimCommon { static void Range_BF(benchmark::State &st); static void Range_HNSW(benchmark::State &st); + // Reproduces allocation/deallocation oscillation issue at block size boundaries. + // Sets up index at blockSize+1 capacity, then repeatedly deletes and re-adds the same vector, + // triggering constant grow-shrink cycles. + // This behavior was fixed by PR #753 with a conservative resize strategy that only + // shrinks containers when there are 2+ free blocks, preventing oscillation cycles. + // Expected: High allocation overhead before fix, stable performance after fix. + static void UpdateAtBlockSize(benchmark::State &st); + private: // Vectors of vector to store deleted labels' data. using LabelData = std::vector>; @@ -64,7 +72,9 @@ void BM_VecSimBasics::AddLabel(benchmark::State &st) { // For tiered index, wait for all threads to finish indexing BM_VecSimGeneral::mock_thread_pool.thread_pool_wait(); - st.counters["memory_per_vector"] = (double)memory_delta / (double)added_vec_count; + st.counters["memory_per_vector"] = + benchmark::Counter((double)memory_delta / (double)added_vec_count, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); st.counters["vectors_per_label"] = vec_per_label; assert(VecSimIndex_IndexSize(index) == N_VECTORS + added_vec_count); @@ -110,7 +120,9 @@ void BM_VecSimBasics::AddLabel_AsyncIngest(benchmark::State &st) { } size_t memory_delta = (INDICES[st.range(0)])->getAllocationSize() - memory_before; - st.counters["memory_per_vector"] = (double)memory_delta / (double)added_vec_count; + st.counters["memory_per_vector"] = + benchmark::Counter((double)memory_delta / (double)added_vec_count, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); st.counters["vectors_per_label"] = vec_per_label; st.counters["num_threads"] = BM_VecSimGeneral::mock_thread_pool.thread_pool_size; @@ -159,7 +171,9 @@ void BM_VecSimBasics::DeleteLabel(algo_t *index, benchmark::State if (VecSimIndex_BasicInfo(index).algo == VecSimAlgo_TIERED) { reinterpret_cast *>(index)->executeReadySwapJobs(); } - st.counters["memory_per_vector"] = memory_delta / (double)removed_vectors_count; + st.counters["memory_per_vector"] = + benchmark::Counter((double)memory_delta / (double)removed_vectors_count, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); // Restore index state. // For each label in removed_labels_data @@ -207,7 +221,10 @@ void BM_VecSimBasics::DeleteLabel_AsyncRepair(benchmark::State &st // Avg. memory delta per vector equals the total memory delta divided by the number // of deleted vectors. int memory_delta = tiered_index->getAllocationSize() - memory_before; - st.counters["memory_per_vector"] = memory_delta / (double)removed_vectors_count; + + st.counters["memory_per_vector"] = + benchmark::Counter((double)memory_delta / (double)removed_vectors_count, + benchmark::Counter::kDefaults, benchmark::Counter::OneK::kIs1024); st.counters["num_threads"] = (double)BM_VecSimGeneral::mock_thread_pool.thread_pool_size; st.counters["num_zombies"] = tiered_index->idToSwapJob.size(); @@ -279,6 +296,69 @@ void BM_VecSimBasics::Range_HNSW(benchmark::State &st) { st.counters["Recall"] = (float)total_res / total_res_bf; } +template +void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { + auto index = GET_INDEX(st.range(0)); + size_t initial_index_size = VecSimIndex_IndexSize(index); + // Calculate vectors needed to reach next block boundary + size_t vecs_to_blocksize = + BM_VecSimGeneral::block_size - (initial_index_size % BM_VecSimGeneral::block_size); + assert(vecs_to_blocksize < BM_VecSimGeneral::block_size); + labelType initial_label_count = index->indexLabelCount(); + labelType curr_label = initial_label_count; + + // Set up index at blockSize+1 to trigger oscillation issue + // Make sure we have enough queries to add a new label. + assert(N_QUERIES > BM_VecSimGeneral::block_size); + size_t overhead = 1; + size_t added_vec_count = vecs_to_blocksize + overhead; + for (size_t i = 0; i < added_vec_count; ++i) { + VecSimIndex_AddVector(index, QUERIES[added_vec_count % N_QUERIES].data(), curr_label++); + } + // For tiered index, wait for all threads to finish indexing + BM_VecSimGeneral::mock_thread_pool->thread_pool_wait(); + assert(VecSimIndex_IndexSize(index) % BM_VecSimGeneral::block_size == overhead); + assert(VecSimIndex_IndexSize(index) == N_VECTORS + added_vec_count); + + std::cout << "Added " << added_vec_count << " vectors to reach block size boundary." + << std::endl; + std::cout << "Index size is now " << VecSimIndex_IndexSize(index) << std::endl; + std::cout << "Last label is " << curr_label - 1 << std::endl; + + // Benchmark loop: repeatedly delete/add same vector to trigger grow-shrink cycles + labelType label_to_update = curr_label - 1; + size_t index_cap = index->indexCapacity(); + for (auto _ : st) { + // Remove the vector directly from hnsw + size_t ret = VecSimIndex_DeleteVector( + GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0)), + label_to_update); + assert(ret == 1); + assert(index->indexCapacity() == index_cap - BM_VecSimGeneral::block_size); + // Capacity should shrink by one block after deletion + ret = VecSimIndex_AddVector(index, QUERIES[(added_vec_count - 1) % N_QUERIES].data(), + label_to_update); + assert(ret == 1); + BM_VecSimGeneral::mock_thread_pool->thread_pool_wait(); + assert(VecSimIndex_IndexSize( + GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0))) == + N_VECTORS + added_vec_count); + // Capacity should grow back to original size after addition + assert(index->indexCapacity() == index_cap); + } + assert(VecSimIndex_IndexSize(index) == N_VECTORS + added_vec_count); + + // Clean-up all the new vectors to restore the index size to its original value. + + size_t new_label_count = index->indexLabelCount(); + for (size_t label = initial_label_count; label < new_label_count; label++) { + // If index is tiered HNSW, remove directly from the underline HNSW. + VecSimIndex_DeleteVector( + GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0)), label); + } + assert(VecSimIndex_IndexSize(index) == N_VECTORS); +} + #define UNIT_AND_ITERATIONS Unit(benchmark::kMillisecond)->Iterations(BM_VecSimGeneral::block_size) // The actual radius will be the given arg divided by 100, since arg must be an integer. @@ -324,3 +404,8 @@ void BM_VecSimBasics::Range_HNSW(benchmark::State &st) { } #define REGISTER_DeleteLabel(BM_FUNC) \ BENCHMARK_REGISTER_F(BM_VecSimBasics, BM_FUNC)->UNIT_AND_ITERATIONS + +#define REGISTER_UpdateAtBlockSize(BM_FUNC, VecSimAlgo) \ + BENCHMARK_REGISTER_F(BM_VecSimBasics, BM_FUNC) \ + ->UNIT_AND_ITERATIONS->Arg(VecSimAlgo) \ + ->ArgName(#VecSimAlgo) diff --git a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp index fd614f49d..6fc335b56 100644 --- a/tests/benchmark/run_files/bm_basics_multi_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_multi_fp32.cpp @@ -33,4 +33,10 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp32_index_t, TieredHNSWI VecSimAlgo_TIERED) #include "benchmark/bm_initialization/bm_basics_initialize_fp32.h" +// Test oscillations at block size boundaries. +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, UpdateAtBlockSize_Multi, fp32_index_t) +(benchmark::State &st) { UpdateAtBlockSize(st); } +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Multi, VecSimAlgo_BF); +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Multi, VecSimAlgo_HNSWLIB); +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Multi, VecSimAlgo_TIERED); BENCHMARK_MAIN(); diff --git a/tests/benchmark/run_files/bm_basics_single_fp32.cpp b/tests/benchmark/run_files/bm_basics_single_fp32.cpp index 889927779..9a264e510 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp32.cpp @@ -31,5 +31,12 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp32_index_t, HNSWIndex_Sin VecSimAlgo_HNSWLIB) DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp32_index_t, TieredHNSWIndex, float, float, VecSimAlgo_TIERED) + +// Test oscillations at block size boundaries. +BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, UpdateAtBlockSize_Single, fp32_index_t) +(benchmark::State &st) { UpdateAtBlockSize(st); } +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_BF); +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_HNSWLIB); +REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_TIERED); #include "benchmark/bm_initialization/bm_basics_initialize_fp32.h" BENCHMARK_MAIN(); From 21e75b45ba6857fd8f3b0632c02bc5d5071fd3bf Mon Sep 17 00:00:00 2001 From: meiravgri Date: Mon, 8 Sep 2025 12:12:35 +0000 Subject: [PATCH 2/3] align with 0.7 api --- tests/benchmark/bm_vecsim_basics.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/benchmark/bm_vecsim_basics.h b/tests/benchmark/bm_vecsim_basics.h index 7a6db8062..a8945406e 100644 --- a/tests/benchmark/bm_vecsim_basics.h +++ b/tests/benchmark/bm_vecsim_basics.h @@ -298,7 +298,7 @@ void BM_VecSimBasics::Range_HNSW(benchmark::State &st) { template void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { - auto index = GET_INDEX(st.range(0)); + auto index = INDICES[st.range(0)]; size_t initial_index_size = VecSimIndex_IndexSize(index); // Calculate vectors needed to reach next block boundary size_t vecs_to_blocksize = @@ -316,7 +316,7 @@ void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { VecSimIndex_AddVector(index, QUERIES[added_vec_count % N_QUERIES].data(), curr_label++); } // For tiered index, wait for all threads to finish indexing - BM_VecSimGeneral::mock_thread_pool->thread_pool_wait(); + BM_VecSimGeneral::mock_thread_pool.thread_pool_wait(); assert(VecSimIndex_IndexSize(index) % BM_VecSimGeneral::block_size == overhead); assert(VecSimIndex_IndexSize(index) == N_VECTORS + added_vec_count); @@ -331,7 +331,7 @@ void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { for (auto _ : st) { // Remove the vector directly from hnsw size_t ret = VecSimIndex_DeleteVector( - GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0)), + INDICES[st.range(0) == VecSimAlgo_TIERED ? VecSimAlgo_HNSWLIB : st.range(0)], label_to_update); assert(ret == 1); assert(index->indexCapacity() == index_cap - BM_VecSimGeneral::block_size); @@ -339,9 +339,9 @@ void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { ret = VecSimIndex_AddVector(index, QUERIES[(added_vec_count - 1) % N_QUERIES].data(), label_to_update); assert(ret == 1); - BM_VecSimGeneral::mock_thread_pool->thread_pool_wait(); + BM_VecSimGeneral::mock_thread_pool.thread_pool_wait(); assert(VecSimIndex_IndexSize( - GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0))) == + INDICES[st.range(0) == VecSimAlgo_TIERED ? VecSimAlgo_HNSWLIB : st.range(0)]) == N_VECTORS + added_vec_count); // Capacity should grow back to original size after addition assert(index->indexCapacity() == index_cap); @@ -354,7 +354,7 @@ void BM_VecSimBasics::UpdateAtBlockSize(benchmark::State &st) { for (size_t label = initial_label_count; label < new_label_count; label++) { // If index is tiered HNSW, remove directly from the underline HNSW. VecSimIndex_DeleteVector( - GET_INDEX(st.range(0) == INDEX_TIERED_HNSW ? INDEX_HNSW : st.range(0)), label); + INDICES[st.range(0) == VecSimAlgo_TIERED ? VecSimAlgo_HNSWLIB : st.range(0)], label); } assert(VecSimIndex_IndexSize(index) == N_VECTORS); } From 61aa8b293de519f0c08228a0b8b542e3eca736ed Mon Sep 17 00:00:00 2001 From: meiravgri Date: Mon, 8 Sep 2025 12:16:07 +0000 Subject: [PATCH 3/3] move UpdateAtBlockSize_Single to the end --- tests/benchmark/run_files/bm_basics_single_fp32.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/benchmark/run_files/bm_basics_single_fp32.cpp b/tests/benchmark/run_files/bm_basics_single_fp32.cpp index 9a264e510..f7dc238d6 100644 --- a/tests/benchmark/run_files/bm_basics_single_fp32.cpp +++ b/tests/benchmark/run_files/bm_basics_single_fp32.cpp @@ -32,11 +32,12 @@ DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, HNSW), fp32_index_t, HNSWIndex_Sin DEFINE_DELETE_LABEL(BM_FUNC_NAME(DeleteLabel, Tiered), fp32_index_t, TieredHNSWIndex, float, float, VecSimAlgo_TIERED) +#include "benchmark/bm_initialization/bm_basics_initialize_fp32.h" + // Test oscillations at block size boundaries. BENCHMARK_TEMPLATE_DEFINE_F(BM_VecSimBasics, UpdateAtBlockSize_Single, fp32_index_t) (benchmark::State &st) { UpdateAtBlockSize(st); } REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_BF); REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_HNSWLIB); REGISTER_UpdateAtBlockSize(UpdateAtBlockSize_Single, VecSimAlgo_TIERED); -#include "benchmark/bm_initialization/bm_basics_initialize_fp32.h" BENCHMARK_MAIN();