@@ -234,6 +234,11 @@ class HNSWIndex : public VecSimIndexAbstract<DistType>,
234
234
double getEpsilon () const ;
235
235
size_t indexSize () const override ;
236
236
size_t indexCapacity () const override ;
237
+ /* *
238
+ * Checks if the index capacity is full to hint the caller a resize is needed.
239
+ * @note Must be called with indexDataGuard locked.
240
+ */
241
+ size_t isCapacityFull () const ;
237
242
size_t getEfConstruction () const ;
238
243
size_t getM () const ;
239
244
size_t getMaxLevel () const ;
@@ -313,6 +318,15 @@ class HNSWIndex : public VecSimIndexAbstract<DistType>,
313
318
*/
314
319
virtual void getDataByLabel (labelType label,
315
320
std::vector<std::vector<DataType>> &vectors_output) const = 0;
321
+
322
+ size_t getStoredVectorsCount () const {
323
+ size_t actual_stored_vec = 0 ;
324
+ for (auto &block : vectorBlocks) {
325
+ actual_stored_vec += block.getLength ();
326
+ }
327
+
328
+ return actual_stored_vec;
329
+ }
316
330
#endif
317
331
318
332
protected:
@@ -358,6 +372,11 @@ size_t HNSWIndex<DataType, DistType>::indexCapacity() const {
358
372
return this ->maxElements ;
359
373
}
360
374
375
+ template <typename DataType, typename DistType>
376
+ size_t HNSWIndex<DataType, DistType>::isCapacityFull() const {
377
+ return indexSize () == this ->maxElements ;
378
+ }
379
+
361
380
template <typename DataType, typename DistType>
362
381
size_t HNSWIndex<DataType, DistType>::getEfConstruction() const {
363
382
return this ->efConstruction ;
@@ -1289,44 +1308,69 @@ template <typename DataType, typename DistType>
1289
1308
void HNSWIndex<DataType, DistType>::resizeIndexCommon(size_t new_max_elements) {
1290
1309
assert (new_max_elements % this ->blockSize == 0 &&
1291
1310
" new_max_elements must be a multiple of blockSize" );
1292
- this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1293
- " Updating HNSW index capacity from %zu to %zu " , this -> maxElements , new_max_elements);
1311
+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING, " Resizing HNSW index from %zu to %zu " ,
1312
+ idToMetaData. capacity () , new_max_elements);
1294
1313
resizeLabelLookup (new_max_elements);
1295
1314
visitedNodesHandlerPool.resize (new_max_elements);
1315
+ assert (idToMetaData.capacity () == idToMetaData.size ());
1296
1316
idToMetaData.resize (new_max_elements);
1297
1317
idToMetaData.shrink_to_fit ();
1298
-
1299
- maxElements = new_max_elements;
1318
+ assert (idToMetaData.capacity () == idToMetaData.size ());
1300
1319
}
1301
1320
1302
1321
template <typename DataType, typename DistType>
1303
1322
void HNSWIndex<DataType, DistType>::growByBlock() {
1304
- size_t new_max_elements = maxElements + this ->blockSize ;
1305
-
1306
1323
// Validations
1307
1324
assert (vectorBlocks.size () == graphDataBlocks.size ());
1308
1325
assert (vectorBlocks.empty () || vectorBlocks.back ().getLength () == this ->blockSize );
1326
+ assert (this ->maxElements % this ->blockSize == 0 );
1327
+ assert (this ->maxElements == indexSize ());
1328
+ assert (graphDataBlocks.size () == this ->maxElements / this ->blockSize );
1329
+ assert (idToMetaData.capacity () == maxElements ||
1330
+ idToMetaData.capacity () == maxElements + this ->blockSize );
1309
1331
1332
+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1333
+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1334
+ maxElements + this ->blockSize );
1335
+ maxElements += this ->blockSize ;
1310
1336
vectorBlocks.emplace_back (this ->blockSize , this ->dataSize , this ->allocator , this ->alignment );
1311
1337
graphDataBlocks.emplace_back (this ->blockSize , this ->elementGraphDataSize , this ->allocator );
1312
1338
1313
- resizeIndexCommon (new_max_elements);
1339
+ if (idToMetaData.capacity () == indexSize ()) {
1340
+ resizeIndexCommon (maxElements);
1341
+ }
1314
1342
}
1315
1343
1316
1344
template <typename DataType, typename DistType>
1317
1345
void HNSWIndex<DataType, DistType>::shrinkByBlock() {
1318
- assert (maxElements >= this ->blockSize );
1319
- size_t new_max_elements = maxElements - this ->blockSize ;
1320
-
1321
- // Validations
1322
- assert (vectorBlocks.size () == graphDataBlocks.size ());
1323
- assert (!vectorBlocks.empty ());
1324
- assert (vectorBlocks.back ().getLength () == 0 );
1325
-
1326
- vectorBlocks.pop_back ();
1327
- graphDataBlocks.pop_back ();
1328
-
1329
- resizeIndexCommon (new_max_elements);
1346
+ assert (this ->maxElements >= this ->blockSize );
1347
+ assert (this ->maxElements % this ->blockSize == 0 );
1348
+ if (indexSize () % this ->blockSize == 0 ) {
1349
+ assert (vectorBlocks.back ().getLength () == 0 );
1350
+ this ->log (VecSimCommonStrings::LOG_VERBOSE_STRING,
1351
+ " Updating HNSW index capacity from %zu to %zu" , maxElements,
1352
+ maxElements - this ->blockSize );
1353
+ vectorBlocks.pop_back ();
1354
+ graphDataBlocks.pop_back ();
1355
+ assert (graphDataBlocks.size () * this ->blockSize == indexSize ());
1356
+
1357
+ if (idToMetaData.capacity () >= (indexSize () + 2 * this ->blockSize )) {
1358
+ resizeIndexCommon (idToMetaData.capacity () - this ->blockSize );
1359
+ } else if (idToMetaData.capacity () == this ->blockSize ) {
1360
+ // Special case to handle last block.
1361
+ // This special condition resolves the ambiguity: when capacity==blockSize, we can't
1362
+ // tell if this block came from growth (should shrink to 0) or initial capacity (should
1363
+ // keep it). We choose to always shrink to 0 to maintain the one-block removal
1364
+ // guarantee. In contrast, newer branches without initial capacity support use simpler
1365
+ // logic: immediately shrink to 0 whenever index size becomes 0.
1366
+ assert (vectorBlocks.empty ());
1367
+ assert (indexSize () == 0 );
1368
+ assert (maxElements == this ->blockSize );
1369
+ resizeIndexCommon (0 );
1370
+ }
1371
+ // Take the lower bound into account.
1372
+ maxElements -= this ->blockSize ;
1373
+ }
1330
1374
}
1331
1375
1332
1376
template <typename DataType, typename DistType>
@@ -1686,9 +1730,7 @@ void HNSWIndex<DataType, DistType>::removeAndSwap(idType internalId) {
1686
1730
1687
1731
// If we need to free a complete block and there is at least one block between the
1688
1732
// capacity and the size.
1689
- if (curElementCount % this ->blockSize == 0 ) {
1690
- shrinkByBlock ();
1691
- }
1733
+ shrinkByBlock ();
1692
1734
}
1693
1735
1694
1736
template <typename DataType, typename DistType>
@@ -1764,6 +1806,16 @@ void HNSWIndex<DataType, DistType>::removeVectorInPlace(const idType element_int
1764
1806
template <typename DataType, typename DistType>
1765
1807
AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
1766
1808
const void *vector_data) {
1809
+ if (isCapacityFull ()) {
1810
+ growByBlock ();
1811
+ } else if (curElementCount % this ->blockSize == 0 ) {
1812
+ // If we had an initial capacity, we might have to initialize new blocks for the data and
1813
+ // meta-data.
1814
+ this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1815
+ this ->alignment );
1816
+ this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1817
+ this ->allocator );
1818
+ }
1767
1819
AddVectorCtx state{};
1768
1820
1769
1821
// Choose randomly the maximum level in which the new element will be in the index.
@@ -1791,17 +1843,6 @@ AddVectorCtx HNSWIndex<DataType, DistType>::storeNewElement(labelType label,
1791
1843
throw e;
1792
1844
}
1793
1845
1794
- if (indexSize () > indexCapacity ()) {
1795
- growByBlock ();
1796
- } else if (state.newElementId % this ->blockSize == 0 ) {
1797
- // If we had an initial capacity, we might have to allocate new blocks for the data and
1798
- // meta-data.
1799
- this ->vectorBlocks .emplace_back (this ->blockSize , this ->dataSize , this ->allocator ,
1800
- this ->alignment );
1801
- this ->graphDataBlocks .emplace_back (this ->blockSize , this ->elementGraphDataSize ,
1802
- this ->allocator );
1803
- }
1804
-
1805
1846
// Insert the new element to the data block
1806
1847
this ->vectorBlocks .back ().addElement (vector_data);
1807
1848
this ->graphDataBlocks .back ().addElement (cur_egd);
0 commit comments