github.com/QuangTung97/bigcache@v0.1.0/segment.go (about) 1 package bigcache 2 3 import ( 4 "github.com/QuangTung97/bigcache/memhash" 5 "sync" 6 "sync/atomic" 7 "unsafe" 8 ) 9 10 type segment struct { 11 mu sync.Mutex 12 rb ringBuf 13 kv map[uint32]int 14 getNow func() uint32 15 16 maxConsecutiveEvacuation int 17 totalAccessTime uint64 18 19 total uint64 20 accessCount uint64 21 hitCount uint64 22 23 _padding [18]byte // for align with cache lines 24 } 25 26 type entryHeader struct { 27 hash uint32 28 accessTime uint32 29 keyLen uint16 30 deleted bool 31 valLen uint32 32 valCap uint32 33 } 34 35 const entryHeaderSize = int(unsafe.Sizeof(entryHeader{})) 36 const entryHeaderAlign = int(unsafe.Alignof(entryHeader{})) 37 const entryHeaderAlignMask = ^uint32(entryHeaderAlign - 1) 38 39 func initSegment(s *segment, bufSize int) { 40 s.rb = newRingBuf(bufSize) 41 s.kv = map[uint32]int{} 42 s.getNow = getNowMono 43 s.maxConsecutiveEvacuation = 5 44 } 45 46 func getNowMono() uint32 { 47 return uint32(memhash.NanoTime() / 1000000000) 48 } 49 50 func (s *segment) put(hash uint32, key []byte, value []byte) { 51 var headerData [entryHeaderSize]byte 52 offset, existed := s.kv[hash] 53 if existed { 54 s.rb.readAt(headerData[:], offset) 55 header := (*entryHeader)(unsafe.Pointer(&headerData[0])) 56 57 s.totalAccessTime -= uint64(header.accessTime) 58 59 if s.keyEqual(header, offset, key) { 60 if len(value) <= int(header.valCap) { 61 s.rb.writeAt(value, offset+entryHeaderSize+int(header.keyLen)) 62 header.valLen = uint32(len(value)) 63 header.accessTime = s.getNow() 64 s.totalAccessTime += uint64(header.accessTime) 65 s.rb.writeAt(headerData[:], offset) 66 return 67 } 68 } 69 header.deleted = true 70 s.rb.writeAt(headerData[:], offset) 71 } 72 73 keyLen := uint16(len(key)) 74 valLen := uint32(len(value)) 75 totalLen := uint32(keyLen) + valLen 76 totalLenAligned := nextNumberAlignToHeader(totalLen) 77 78 totalSize := entryHeaderSize + int(totalLenAligned) 79 s.evacuate(totalSize) 80 81 header := (*entryHeader)(unsafe.Pointer(&headerData[0])) 82 header.hash = hash 83 header.accessTime = s.getNow() 84 header.keyLen = keyLen 85 header.deleted = false 86 header.valLen = valLen 87 header.valCap = totalLenAligned - uint32(keyLen) 88 89 offset = s.rb.append(headerData[:]) 90 s.rb.append(key) 91 s.rb.append(value) 92 s.rb.appendEmpty(int(header.valCap - header.valLen)) 93 s.kv[hash] = offset 94 95 if !existed { 96 atomic.AddUint64(&s.total, 1) 97 } 98 s.totalAccessTime += uint64(header.accessTime) 99 } 100 101 func (s *segment) evacuate(expectedSize int) { 102 var headerData [entryHeaderSize]byte 103 consecutiveEvacuation := 0 104 105 for s.rb.getAvailable() < expectedSize { 106 offset := s.rb.getBegin() 107 s.rb.readAt(headerData[:], offset) 108 header := (*entryHeader)(unsafe.Pointer(&headerData[0])) 109 110 size := entryHeaderSize + int(header.keyLen) + int(header.valCap) 111 112 expired := atomic.LoadUint64(&s.total)*uint64(header.accessTime) < s.totalAccessTime 113 if header.deleted || expired || consecutiveEvacuation >= s.maxConsecutiveEvacuation { 114 consecutiveEvacuation = 0 115 s.rb.skip(size) 116 if !header.deleted { 117 delete(s.kv, header.hash) 118 atomic.AddUint64(&s.total, ^uint64(0)) 119 s.totalAccessTime -= uint64(header.accessTime) 120 } 121 } else { 122 prevEnd := s.rb.evacuate(size) 123 s.kv[header.hash] = prevEnd 124 consecutiveEvacuation++ 125 } 126 } 127 } 128 129 func (s *segment) get(hash uint32, key []byte, value []byte) (n int, ok bool) { 130 atomic.AddUint64(&s.accessCount, 1) 131 offset, ok := s.kv[hash] 132 if !ok { 133 return 0, false 134 } 135 136 var headerData [entryHeaderSize]byte 137 s.rb.readAt(headerData[:], offset) 138 header := (*entryHeader)(unsafe.Pointer(&headerData[0])) 139 if !s.keyEqual(header, offset, key) { 140 return 0, false 141 } 142 143 atomic.AddUint64(&s.hitCount, 1) 144 145 readLen := int(header.valLen) 146 if readLen > len(value) { 147 readLen = len(value) 148 } 149 s.rb.readAt(value[:readLen], offset+entryHeaderSize+int(header.keyLen)) 150 151 s.totalAccessTime -= uint64(header.accessTime) 152 header.accessTime = s.getNow() 153 s.rb.writeAt(headerData[:], offset) 154 s.totalAccessTime += uint64(header.accessTime) 155 156 return int(header.valLen), true 157 } 158 159 func (s *segment) delete(hash uint32, key []byte) bool { 160 offset, ok := s.kv[hash] 161 if !ok { 162 return false 163 } 164 165 var headerData [entryHeaderSize]byte 166 s.rb.readAt(headerData[:], offset) 167 header := (*entryHeader)(unsafe.Pointer(&headerData[0])) 168 if !s.keyEqual(header, offset, key) { 169 return false 170 } 171 172 header.deleted = true 173 s.rb.writeAt(headerData[:], offset) 174 delete(s.kv, hash) 175 atomic.AddUint64(&s.total, ^uint64(0)) 176 s.totalAccessTime -= uint64(header.accessTime) 177 178 return true 179 } 180 181 func (s *segment) keyEqual(header *entryHeader, offset int, key []byte) bool { 182 if int(header.keyLen) != len(key) { 183 return false 184 } 185 if ok := s.rb.bytesEqual(offset+entryHeaderSize, key); !ok { 186 return false 187 } 188 return true 189 } 190 191 func (s *segment) getTotal() uint64 { 192 return atomic.LoadUint64(&s.total) 193 } 194 195 func (s *segment) getHitCount() uint64 { 196 return atomic.LoadUint64(&s.hitCount) 197 } 198 199 func (s *segment) getAccessCount() uint64 { 200 return atomic.LoadUint64(&s.accessCount) 201 } 202 203 func nextNumberAlignToHeader(n uint32) uint32 { 204 return (n + uint32(entryHeaderAlign) - 1) & entryHeaderAlignMask 205 }