github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/vector/hnsw/vertex.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package hnsw 13 14 import ( 15 "sync" 16 ) 17 18 type vertex struct { 19 id uint64 20 sync.Mutex 21 level int 22 connections [][]uint64 23 maintenance bool 24 } 25 26 func (v *vertex) markAsMaintenance() { 27 v.Lock() 28 v.maintenance = true 29 v.Unlock() 30 } 31 32 func (v *vertex) unmarkAsMaintenance() { 33 v.Lock() 34 v.maintenance = false 35 v.Unlock() 36 } 37 38 func (v *vertex) isUnderMaintenance() bool { 39 v.Lock() 40 m := v.maintenance 41 v.Unlock() 42 return m 43 } 44 45 func (v *vertex) connectionsAtLevelNoLock(level int) []uint64 { 46 return v.connections[level] 47 } 48 49 func (v *vertex) upgradeToLevelNoLock(level int) { 50 newConnections := make([][]uint64, level+1) 51 copy(newConnections, v.connections) 52 v.level = level 53 v.connections = newConnections 54 } 55 56 func (v *vertex) setConnectionsAtLevel(level int, connections []uint64) { 57 v.Lock() 58 defer v.Unlock() 59 60 // before we simply copy the connections let's check how much smaller the new 61 // list is. If it's considerably smaller, we might want to downsize the 62 // current allocation 63 oldCap := cap(v.connections[level]) 64 newLen := len(connections) 65 ratio := float64(1) - float64(newLen)/float64(oldCap) 66 if ratio > 0.33 || oldCap < newLen { 67 // the replaced slice is over 33% smaller than the last one, this makes it 68 // worth to replace it entirely. This has a small performance cost, it 69 // means that if we need append to this node again, we need to re-allocate, 70 // but we gain at least a 33% memory saving on this particular node right 71 // away. 72 v.connections[level] = connections 73 return 74 } 75 76 v.connections[level] = v.connections[level][:newLen] 77 copy(v.connections[level], connections) 78 } 79 80 func (v *vertex) appendConnectionAtLevelNoLock(level int, connection uint64, maxConns int) { 81 if len(v.connections[level]) == cap(v.connections[level]) { 82 // if the len is the capacity, this means a new array needs to be 83 // allocated to back this slice. The go runtime would do this 84 // automatically, if we just use 'append', but it wouldn't do it very 85 // efficiently. It would always double the existing capacity. Since we have 86 // a hard limit (maxConns), we don't ever want to grow it beyond that. In 87 // the worst case, the current len & cap could be maxConns-1, which would 88 // mean we would double to 2*(maxConns-1) which would be way too large. 89 // 90 // Instead let's grow in 4 steps: 25%, 50%, 75% or full capacity 91 ratio := float64(len(v.connections[level])) / float64(maxConns) 92 93 target := 0 94 switch { 95 case ratio < 0.25: 96 target = int(float64(0.25) * float64(maxConns)) 97 case ratio < 0.50: 98 target = int(float64(0.50) * float64(maxConns)) 99 case ratio < 0.75: 100 target = int(float64(0.75) * float64(maxConns)) 101 default: 102 target = maxConns 103 } 104 105 // handle rounding errors on maxConns not cleanly divisible by 4 106 if target < len(v.connections[level])+1 { 107 target = len(v.connections[level]) + 1 108 } 109 110 newConns := make([]uint64, len(v.connections[level]), target) 111 copy(newConns, v.connections[level]) 112 v.connections[level] = newConns 113 } 114 115 v.connections[level] = append(v.connections[level], connection) 116 } 117 118 func (v *vertex) resetConnectionsAtLevelNoLock(level int) { 119 v.connections[level] = v.connections[level][:0] 120 }