segment.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. package freecache
  2. import (
  3. "errors"
  4. "time"
  5. "unsafe"
  6. )
  7. const HASH_ENTRY_SIZE = 16
  8. const ENTRY_HDR_SIZE = 24
  9. var ErrLargeKey = errors.New("The key is larger than 65535")
  10. var ErrLargeEntry = errors.New("The entry size is larger than 1/1024 of cache size")
  11. var ErrNotFound = errors.New("Entry not found")
  12. // entry pointer struct points to an entry in ring buffer
  13. type entryPtr struct {
  14. offset int64 // entry offset in ring buffer
  15. hash16 uint16 // entries are ordered by hash16 in a slot.
  16. keyLen uint16 // used to compare a key
  17. reserved uint32
  18. }
  19. // entry header struct in ring buffer, followed by key and value.
  20. type entryHdr struct {
  21. accessTime uint32
  22. expireAt uint32
  23. keyLen uint16
  24. hash16 uint16
  25. valLen uint32
  26. valCap uint32
  27. deleted bool
  28. slotId uint8
  29. reserved uint16
  30. }
  31. // a segment contains 256 slots, a slot is an array of entry pointers ordered by hash16 value
  32. // the entry can be looked up by hash value of the key.
  33. type segment struct {
  34. rb RingBuf // ring buffer that stores data
  35. segId int
  36. entryCount int64
  37. totalCount int64 // number of entries in ring buffer, including deleted entries.
  38. totalTime int64 // used to calculate least recent used entry.
  39. totalEvacuate int64 // used for debug
  40. overwrites int64 // used for debug
  41. vacuumLen int64 // up to vacuumLen, new data can be written without overwriting old data.
  42. slotLens [256]int32 // The actual length for every slot.
  43. slotCap int32 // max number of entry pointers a slot can hold.
  44. slotsData []entryPtr // shared by all 256 slots
  45. }
  46. func newSegment(bufSize int, segId int) (seg segment) {
  47. seg.rb = NewRingBuf(bufSize, 0)
  48. seg.segId = segId
  49. seg.vacuumLen = int64(bufSize)
  50. seg.slotCap = 1
  51. seg.slotsData = make([]entryPtr, 256*seg.slotCap)
  52. return
  53. }
  54. func (seg *segment) set(key, value []byte, hashVal uint64, expireSeconds int) (err error) {
  55. if len(key) > 65535 {
  56. return ErrLargeKey
  57. }
  58. maxKeyValLen := len(seg.rb.data)/4 - ENTRY_HDR_SIZE
  59. if len(key)+len(value) > maxKeyValLen {
  60. // Do not accept large entry.
  61. return ErrLargeEntry
  62. }
  63. now := uint32(time.Now().Unix())
  64. expireAt := uint32(0)
  65. if expireSeconds > 0 {
  66. expireAt = now + uint32(expireSeconds)
  67. }
  68. slotId := uint8(hashVal >> 8)
  69. hash16 := uint16(hashVal >> 16)
  70. var hdrBuf [ENTRY_HDR_SIZE]byte
  71. hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
  72. slotOff := int32(slotId) * seg.slotCap
  73. slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  74. idx, match := seg.lookup(slot, hash16, key)
  75. if match {
  76. matchedPtr := &slot[idx]
  77. seg.rb.ReadAt(hdrBuf[:], matchedPtr.offset)
  78. hdr.slotId = slotId
  79. hdr.hash16 = hash16
  80. hdr.keyLen = uint16(len(key))
  81. hdr.accessTime = now
  82. hdr.expireAt = expireAt
  83. hdr.valLen = uint32(len(value))
  84. if hdr.valCap >= hdr.valLen {
  85. //in place overwrite
  86. seg.totalTime += int64(hdr.accessTime) - int64(now)
  87. seg.rb.WriteAt(hdrBuf[:], matchedPtr.offset)
  88. seg.rb.WriteAt(value, matchedPtr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
  89. seg.overwrites++
  90. return
  91. }
  92. // increase capacity and limit entry len.
  93. for hdr.valCap < hdr.valLen {
  94. hdr.valCap *= 2
  95. }
  96. if hdr.valCap > uint32(maxKeyValLen-len(key)) {
  97. hdr.valCap = uint32(maxKeyValLen - len(key))
  98. }
  99. } else {
  100. hdr.slotId = slotId
  101. hdr.hash16 = hash16
  102. hdr.keyLen = uint16(len(key))
  103. hdr.accessTime = now
  104. hdr.expireAt = expireAt
  105. hdr.valLen = uint32(len(value))
  106. hdr.valCap = uint32(len(value))
  107. if hdr.valCap == 0 { // avoid infinite loop when increasing capacity.
  108. hdr.valCap = 1
  109. }
  110. }
  111. entryLen := ENTRY_HDR_SIZE + int64(len(key)) + int64(hdr.valCap)
  112. slotModified := seg.evacuate(entryLen, slotId, now)
  113. if slotModified {
  114. // the slot has been modified during evacuation, we need to looked up for the 'idx' again.
  115. // otherwise there would be index out of bound error.
  116. slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  117. idx, match = seg.lookup(slot, hash16, key)
  118. }
  119. newOff := seg.rb.End()
  120. if match {
  121. seg.updateEntryPtr(slotId, hash16, slot[idx].offset, newOff)
  122. } else {
  123. seg.insertEntryPtr(slotId, hash16, newOff, idx, hdr.keyLen)
  124. }
  125. seg.rb.Write(hdrBuf[:])
  126. seg.rb.Write(key)
  127. seg.rb.Write(value)
  128. seg.rb.Skip(int64(hdr.valCap - hdr.valLen))
  129. seg.totalTime += int64(now)
  130. seg.totalCount++
  131. seg.vacuumLen -= entryLen
  132. return
  133. }
  134. func (seg *segment) evacuate(entryLen int64, slotId uint8, now uint32) (slotModified bool) {
  135. var oldHdrBuf [ENTRY_HDR_SIZE]byte
  136. consecutiveEvacuate := 0
  137. for seg.vacuumLen < entryLen {
  138. oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size()
  139. seg.rb.ReadAt(oldHdrBuf[:], oldOff)
  140. oldHdr := (*entryHdr)(unsafe.Pointer(&oldHdrBuf[0]))
  141. oldEntryLen := ENTRY_HDR_SIZE + int64(oldHdr.keyLen) + int64(oldHdr.valCap)
  142. if oldHdr.deleted {
  143. consecutiveEvacuate = 0
  144. seg.totalTime -= int64(oldHdr.accessTime)
  145. seg.totalCount--
  146. seg.vacuumLen += oldEntryLen
  147. continue
  148. }
  149. expired := oldHdr.expireAt != 0 && oldHdr.expireAt < now
  150. leastRecentUsed := int64(oldHdr.accessTime)*seg.totalCount <= seg.totalTime
  151. if expired || leastRecentUsed || consecutiveEvacuate > 5 {
  152. seg.delEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff)
  153. if oldHdr.slotId == slotId {
  154. slotModified = true
  155. }
  156. consecutiveEvacuate = 0
  157. seg.totalTime -= int64(oldHdr.accessTime)
  158. seg.totalCount--
  159. seg.vacuumLen += oldEntryLen
  160. } else {
  161. // evacuate an old entry that has been accessed recently for better cache hit rate.
  162. newOff := seg.rb.Evacuate(oldOff, int(oldEntryLen))
  163. seg.updateEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff, newOff)
  164. consecutiveEvacuate++
  165. seg.totalEvacuate++
  166. }
  167. }
  168. return
  169. }
  170. func (seg *segment) get(key []byte, hashVal uint64) (value []byte, err error) {
  171. slotId := uint8(hashVal >> 8)
  172. hash16 := uint16(hashVal >> 16)
  173. slotOff := int32(slotId) * seg.slotCap
  174. var slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  175. idx, match := seg.lookup(slot, hash16, key)
  176. if !match {
  177. err = ErrNotFound
  178. return
  179. }
  180. ptr := &slot[idx]
  181. now := uint32(time.Now().Unix())
  182. var hdrBuf [ENTRY_HDR_SIZE]byte
  183. seg.rb.ReadAt(hdrBuf[:], ptr.offset)
  184. hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
  185. if hdr.expireAt != 0 && hdr.expireAt <= now {
  186. seg.delEntryPtr(slotId, hash16, ptr.offset)
  187. err = ErrNotFound
  188. return
  189. }
  190. seg.totalTime += int64(now - hdr.accessTime)
  191. hdr.accessTime = now
  192. seg.rb.WriteAt(hdrBuf[:], ptr.offset)
  193. value = make([]byte, hdr.valLen)
  194. seg.rb.ReadAt(value, ptr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
  195. return
  196. }
  197. func (seg *segment) del(key []byte, hashVal uint64) (affected bool) {
  198. slotId := uint8(hashVal >> 8)
  199. hash16 := uint16(hashVal >> 16)
  200. slotOff := int32(slotId) * seg.slotCap
  201. slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  202. idx, match := seg.lookup(slot, hash16, key)
  203. if !match {
  204. return false
  205. }
  206. ptr := &slot[idx]
  207. seg.delEntryPtr(slotId, hash16, ptr.offset)
  208. return true
  209. }
  210. func (seg *segment) expand() {
  211. newSlotData := make([]entryPtr, seg.slotCap*2*256)
  212. for i := 0; i < 256; i++ {
  213. off := int32(i) * seg.slotCap
  214. copy(newSlotData[off*2:], seg.slotsData[off:off+seg.slotLens[i]])
  215. }
  216. seg.slotCap *= 2
  217. seg.slotsData = newSlotData
  218. }
  219. func (seg *segment) updateEntryPtr(slotId uint8, hash16 uint16, oldOff, newOff int64) {
  220. slotOff := int32(slotId) * seg.slotCap
  221. slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  222. idx, match := seg.lookupByOff(slot, hash16, oldOff)
  223. if !match {
  224. return
  225. }
  226. ptr := &slot[idx]
  227. ptr.offset = newOff
  228. }
  229. func (seg *segment) insertEntryPtr(slotId uint8, hash16 uint16, offset int64, idx int, keyLen uint16) {
  230. slotOff := int32(slotId) * seg.slotCap
  231. if seg.slotLens[slotId] == seg.slotCap {
  232. seg.expand()
  233. slotOff *= 2
  234. }
  235. seg.slotLens[slotId]++
  236. seg.entryCount++
  237. slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  238. copy(slot[idx+1:], slot[idx:])
  239. slot[idx].offset = offset
  240. slot[idx].hash16 = hash16
  241. slot[idx].keyLen = keyLen
  242. }
  243. func (seg *segment) delEntryPtr(slotId uint8, hash16 uint16, offset int64) {
  244. slotOff := int32(slotId) * seg.slotCap
  245. slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
  246. idx, match := seg.lookupByOff(slot, hash16, offset)
  247. if !match {
  248. return
  249. }
  250. var entryHdrBuf [ENTRY_HDR_SIZE]byte
  251. seg.rb.ReadAt(entryHdrBuf[:], offset)
  252. entryHdr := (*entryHdr)(unsafe.Pointer(&entryHdrBuf[0]))
  253. entryHdr.deleted = true
  254. seg.rb.WriteAt(entryHdrBuf[:], offset)
  255. copy(slot[idx:], slot[idx+1:])
  256. seg.slotLens[slotId]--
  257. seg.entryCount--
  258. }
  259. func entryPtrIdx(slot []entryPtr, hash16 uint16) (idx int) {
  260. high := len(slot)
  261. for idx < high {
  262. mid := (idx + high) >> 1
  263. oldEntry := &slot[mid]
  264. if oldEntry.hash16 < hash16 {
  265. idx = mid + 1
  266. } else {
  267. high = mid
  268. }
  269. }
  270. return
  271. }
  272. func (seg *segment) lookup(slot []entryPtr, hash16 uint16, key []byte) (idx int, match bool) {
  273. idx = entryPtrIdx(slot, hash16)
  274. for idx < len(slot) {
  275. ptr := &slot[idx]
  276. if ptr.hash16 != hash16 {
  277. break
  278. }
  279. match = int(ptr.keyLen) == len(key) && seg.rb.EqualAt(key, ptr.offset+ENTRY_HDR_SIZE)
  280. if match {
  281. return
  282. }
  283. idx++
  284. }
  285. return
  286. }
  287. func (seg *segment) lookupByOff(slot []entryPtr, hash16 uint16, offset int64) (idx int, match bool) {
  288. idx = entryPtrIdx(slot, hash16)
  289. for idx < len(slot) {
  290. ptr := &slot[idx]
  291. if ptr.hash16 != hash16 {
  292. break
  293. }
  294. match = ptr.offset == offset
  295. if match {
  296. return
  297. }
  298. idx++
  299. }
  300. return
  301. }