@@ -12,7 +12,6 @@ package hnswgo
1212import "C"
1313import (
1414 "math"
15- "runtime"
1615 "sync"
1716 "unsafe"
1817)
@@ -86,7 +85,6 @@ func (h *HNSW) Free() bool {
8685 }
8786 C .freeHNSW (h .index , spaceChar (h .spaceType ))
8887 h .index = nil
89- runtime .GC ()
9088 return true
9189}
9290
@@ -107,17 +105,16 @@ func (h *HNSW) Save(location string) bool {
107105 return true
108106}
109107
110- // normalizeVector normalize vector
111- func normalizeVector (vector []float32 ) [] float32 {
112- var norm float32
113- for i := 0 ; i < len ( vector ); i ++ {
114- norm += vector [ i ] * vector [ i ]
108+ // normalizeVector normalizes a vector in-place to unit length.
109+ func normalizeVector (vector []float32 ) {
110+ var squaredSum float32
111+ for _ , v := range vector {
112+ squaredSum += v * v
115113 }
116- norm = 1.0 / (float32 ( math .Sqrt (float64 (norm ))) + 1e-15 )
117- for i := 0 ; i < len ( vector ); i ++ {
118- vector [i ] = vector [ i ] * norm
114+ invNorm := float32 ( 1.0 / (math .Sqrt (float64 (squaredSum )) + 1e-15 ) )
115+ for i := range vector {
116+ vector [i ] *= invNorm
119117 }
120- return vector
121118}
122119
123120// AddPoint adds a point to the index.
@@ -126,7 +123,7 @@ func (h *HNSW) AddPoint(vector []float32, label uint32) bool {
126123 return false
127124 }
128125 if h .normalize {
129- vector = normalizeVector (vector )
126+ normalizeVector (vector )
130127 }
131128 C .addPoint (h .index , (* C .float )(unsafe .Pointer (& vector [0 ])), C .uint64_t (label ), C .bool (false ))
132129 return true
@@ -139,7 +136,7 @@ func (h *HNSW) AddPointWithReplace(vector []float32, label uint32) bool {
139136 return false
140137 }
141138 if h .normalize {
142- vector = normalizeVector (vector )
139+ normalizeVector (vector )
143140 }
144141 C .addPoint (h .index , (* C .float )(unsafe .Pointer (& vector [0 ])), C .uint64_t (label ), C .bool (true ))
145142 return true
@@ -172,55 +169,77 @@ func (h *HNSW) AddBatchPoints(vectors [][]float32, labels []uint32, coroutines i
172169 return true
173170}
174171
175- // SearchKNN search points on graph with knn-algorithm
172+ // searchBufferPool reuses C-type slices to reduce allocations in SearchKNN.
173+ var searchBufferPool = sync.Pool {
174+ New : func () any {
175+ return & searchBuffer {}
176+ },
177+ }
178+
179+ type searchBuffer struct {
180+ labels []C.uint64_t
181+ dists []C.float
182+ }
183+
184+ // SearchKNN searches for the N nearest neighbors of the given vector.
176185func (h * HNSW ) SearchKNN (vector []float32 , N int ) ([]uint32 , []float32 ) {
177186 if h .index == nil {
178187 return nil , nil
179188 }
180- Clabel := make ([]C.uint64_t , N , N )
181- Cdist := make ([]C.float , N , N )
182189 if h .normalize {
183- vector = normalizeVector (vector )
190+ normalizeVector (vector )
184191 }
185- numResult := int (C .searchKnn (h .index , (* C .float )(unsafe .Pointer (& vector [0 ])), C .int (N ), & Clabel [0 ], & Cdist [0 ]))
186- labels := make ([]uint32 , N )
187- dists := make ([]float32 , N )
192+
193+ buf := searchBufferPool .Get ().(* searchBuffer )
194+ if cap (buf .labels ) < N {
195+ buf .labels = make ([]C.uint64_t , N )
196+ buf .dists = make ([]C.float , N )
197+ } else {
198+ buf .labels = buf .labels [:N ]
199+ buf .dists = buf .dists [:N ]
200+ }
201+
202+ numResult := int (C .searchKnn (h .index , (* C .float )(unsafe .Pointer (& vector [0 ])), C .int (N ), & buf .labels [0 ], & buf .dists [0 ]))
203+
204+ labels := make ([]uint32 , numResult )
205+ dists := make ([]float32 , numResult )
188206 for i := 0 ; i < numResult ; i ++ {
189- labels [i ] = uint32 (Clabel [i ])
190- dists [i ] = float32 (Cdist [i ])
207+ labels [i ] = uint32 (buf . labels [i ])
208+ dists [i ] = float32 (buf . dists [i ])
191209 }
192- return labels [:numResult ], dists [:numResult ]
210+
211+ searchBufferPool .Put (buf )
212+ return labels , dists
193213}
194214
195- // SearchBatchKNN search multiple points on graph with knn-algorithm
215+ // SearchBatchKNN searches for the N nearest neighbors of multiple vectors concurrently.
196216func (h * HNSW ) SearchBatchKNN (vectors [][]float32 , N , coroutines int ) ([][]uint32 , [][]float32 ) {
217+ totalVectors := len (vectors )
197218 if coroutines < 1 {
198219 coroutines = 1
199220 }
221+ if coroutines > totalVectors {
222+ coroutines = totalVectors
223+ }
200224
201- var lock sync.Mutex
202- labelList := make ([][]uint32 , len (vectors ))
203- distList := make ([][]float32 , len (vectors ))
225+ labelList := make ([][]uint32 , totalVectors )
226+ distList := make ([][]float32 , totalVectors )
204227
205- b := len ( vectors ) / coroutines
228+ batchSize := totalVectors / coroutines
206229 var wg sync.WaitGroup
207230 for i := 0 ; i < coroutines ; i ++ {
208- wg .Add (1 )
209-
210- end := (i + 1 ) * b
211- if i == coroutines - 1 && len (vectors ) > end {
212- end = len (vectors )
231+ start := i * batchSize
232+ end := start + batchSize
233+ if i == coroutines - 1 {
234+ end = totalVectors
213235 }
214- go func (i int ) {
236+ wg .Add (1 )
237+ go func (start , end int ) {
215238 defer wg .Done ()
216- for j := i * b ; j < end ; j ++ {
217- labels , dist := h .SearchKNN (vectors [j ], N )
218- lock .Lock ()
219- labelList [j ] = labels
220- distList [j ] = dist
221- lock .Unlock ()
239+ for j := start ; j < end ; j ++ {
240+ labelList [j ], distList [j ] = h .SearchKNN (vectors [j ], N )
222241 }
223- }(i )
242+ }(start , end )
224243 }
225244 wg .Wait ()
226245 return labelList , distList
0 commit comments