123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- package freecache
- import (
- "errors"
- "time"
- "unsafe"
- )
- const HASH_ENTRY_SIZE = 16
- const ENTRY_HDR_SIZE = 24
- var ErrLargeKey = errors.New("The key is larger than 65535")
- var ErrLargeEntry = errors.New("The entry size is larger than 1/1024 of cache size")
- var ErrNotFound = errors.New("Entry not found")
- // entry pointer struct points to an entry in ring buffer
- type entryPtr struct {
- offset int64 // entry offset in ring buffer
- hash16 uint16 // entries are ordered by hash16 in a slot.
- keyLen uint16 // used to compare a key
- reserved uint32
- }
- // entry header struct in ring buffer, followed by key and value.
- type entryHdr struct {
- accessTime uint32
- expireAt uint32
- keyLen uint16
- hash16 uint16
- valLen uint32
- valCap uint32
- deleted bool
- slotId uint8
- reserved uint16
- }
- // a segment contains 256 slots, a slot is an array of entry pointers ordered by hash16 value
- // the entry can be looked up by hash value of the key.
- type segment struct {
- rb RingBuf // ring buffer that stores data
- segId int
- entryCount int64
- totalCount int64 // number of entries in ring buffer, including deleted entries.
- totalTime int64 // used to calculate least recent used entry.
- totalEvacuate int64 // used for debug
- overwrites int64 // used for debug
- vacuumLen int64 // up to vacuumLen, new data can be written without overwriting old data.
- slotLens [256]int32 // The actual length for every slot.
- slotCap int32 // max number of entry pointers a slot can hold.
- slotsData []entryPtr // shared by all 256 slots
- }
- func newSegment(bufSize int, segId int) (seg segment) {
- seg.rb = NewRingBuf(bufSize, 0)
- seg.segId = segId
- seg.vacuumLen = int64(bufSize)
- seg.slotCap = 1
- seg.slotsData = make([]entryPtr, 256*seg.slotCap)
- return
- }
- func (seg *segment) set(key, value []byte, hashVal uint64, expireSeconds int) (err error) {
- if len(key) > 65535 {
- return ErrLargeKey
- }
- maxKeyValLen := len(seg.rb.data)/4 - ENTRY_HDR_SIZE
- if len(key)+len(value) > maxKeyValLen {
- // Do not accept large entry.
- return ErrLargeEntry
- }
- now := uint32(time.Now().Unix())
- expireAt := uint32(0)
- if expireSeconds > 0 {
- expireAt = now + uint32(expireSeconds)
- }
- slotId := uint8(hashVal >> 8)
- hash16 := uint16(hashVal >> 16)
- var hdrBuf [ENTRY_HDR_SIZE]byte
- hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
- slotOff := int32(slotId) * seg.slotCap
- slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match := seg.lookup(slot, hash16, key)
- if match {
- matchedPtr := &slot[idx]
- seg.rb.ReadAt(hdrBuf[:], matchedPtr.offset)
- hdr.slotId = slotId
- hdr.hash16 = hash16
- hdr.keyLen = uint16(len(key))
- hdr.accessTime = now
- hdr.expireAt = expireAt
- hdr.valLen = uint32(len(value))
- if hdr.valCap >= hdr.valLen {
- //in place overwrite
- seg.totalTime += int64(hdr.accessTime) - int64(now)
- seg.rb.WriteAt(hdrBuf[:], matchedPtr.offset)
- seg.rb.WriteAt(value, matchedPtr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
- seg.overwrites++
- return
- }
- // increase capacity and limit entry len.
- for hdr.valCap < hdr.valLen {
- hdr.valCap *= 2
- }
- if hdr.valCap > uint32(maxKeyValLen-len(key)) {
- hdr.valCap = uint32(maxKeyValLen - len(key))
- }
- } else {
- hdr.slotId = slotId
- hdr.hash16 = hash16
- hdr.keyLen = uint16(len(key))
- hdr.accessTime = now
- hdr.expireAt = expireAt
- hdr.valLen = uint32(len(value))
- hdr.valCap = uint32(len(value))
- if hdr.valCap == 0 { // avoid infinite loop when increasing capacity.
- hdr.valCap = 1
- }
- }
- entryLen := ENTRY_HDR_SIZE + int64(len(key)) + int64(hdr.valCap)
- slotModified := seg.evacuate(entryLen, slotId, now)
- if slotModified {
- // the slot has been modified during evacuation, we need to looked up for the 'idx' again.
- // otherwise there would be index out of bound error.
- slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match = seg.lookup(slot, hash16, key)
- }
- newOff := seg.rb.End()
- if match {
- seg.updateEntryPtr(slotId, hash16, slot[idx].offset, newOff)
- } else {
- seg.insertEntryPtr(slotId, hash16, newOff, idx, hdr.keyLen)
- }
- seg.rb.Write(hdrBuf[:])
- seg.rb.Write(key)
- seg.rb.Write(value)
- seg.rb.Skip(int64(hdr.valCap - hdr.valLen))
- seg.totalTime += int64(now)
- seg.totalCount++
- seg.vacuumLen -= entryLen
- return
- }
- func (seg *segment) evacuate(entryLen int64, slotId uint8, now uint32) (slotModified bool) {
- var oldHdrBuf [ENTRY_HDR_SIZE]byte
- consecutiveEvacuate := 0
- for seg.vacuumLen < entryLen {
- oldOff := seg.rb.End() + seg.vacuumLen - seg.rb.Size()
- seg.rb.ReadAt(oldHdrBuf[:], oldOff)
- oldHdr := (*entryHdr)(unsafe.Pointer(&oldHdrBuf[0]))
- oldEntryLen := ENTRY_HDR_SIZE + int64(oldHdr.keyLen) + int64(oldHdr.valCap)
- if oldHdr.deleted {
- consecutiveEvacuate = 0
- seg.totalTime -= int64(oldHdr.accessTime)
- seg.totalCount--
- seg.vacuumLen += oldEntryLen
- continue
- }
- expired := oldHdr.expireAt != 0 && oldHdr.expireAt < now
- leastRecentUsed := int64(oldHdr.accessTime)*seg.totalCount <= seg.totalTime
- if expired || leastRecentUsed || consecutiveEvacuate > 5 {
- seg.delEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff)
- if oldHdr.slotId == slotId {
- slotModified = true
- }
- consecutiveEvacuate = 0
- seg.totalTime -= int64(oldHdr.accessTime)
- seg.totalCount--
- seg.vacuumLen += oldEntryLen
- } else {
- // evacuate an old entry that has been accessed recently for better cache hit rate.
- newOff := seg.rb.Evacuate(oldOff, int(oldEntryLen))
- seg.updateEntryPtr(oldHdr.slotId, oldHdr.hash16, oldOff, newOff)
- consecutiveEvacuate++
- seg.totalEvacuate++
- }
- }
- return
- }
- func (seg *segment) get(key []byte, hashVal uint64) (value []byte, err error) {
- slotId := uint8(hashVal >> 8)
- hash16 := uint16(hashVal >> 16)
- slotOff := int32(slotId) * seg.slotCap
- var slot = seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match := seg.lookup(slot, hash16, key)
- if !match {
- err = ErrNotFound
- return
- }
- ptr := &slot[idx]
- now := uint32(time.Now().Unix())
- var hdrBuf [ENTRY_HDR_SIZE]byte
- seg.rb.ReadAt(hdrBuf[:], ptr.offset)
- hdr := (*entryHdr)(unsafe.Pointer(&hdrBuf[0]))
- if hdr.expireAt != 0 && hdr.expireAt <= now {
- seg.delEntryPtr(slotId, hash16, ptr.offset)
- err = ErrNotFound
- return
- }
- seg.totalTime += int64(now - hdr.accessTime)
- hdr.accessTime = now
- seg.rb.WriteAt(hdrBuf[:], ptr.offset)
- value = make([]byte, hdr.valLen)
- seg.rb.ReadAt(value, ptr.offset+ENTRY_HDR_SIZE+int64(hdr.keyLen))
- return
- }
- func (seg *segment) del(key []byte, hashVal uint64) (affected bool) {
- slotId := uint8(hashVal >> 8)
- hash16 := uint16(hashVal >> 16)
- slotOff := int32(slotId) * seg.slotCap
- slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match := seg.lookup(slot, hash16, key)
- if !match {
- return false
- }
- ptr := &slot[idx]
- seg.delEntryPtr(slotId, hash16, ptr.offset)
- return true
- }
- func (seg *segment) expand() {
- newSlotData := make([]entryPtr, seg.slotCap*2*256)
- for i := 0; i < 256; i++ {
- off := int32(i) * seg.slotCap
- copy(newSlotData[off*2:], seg.slotsData[off:off+seg.slotLens[i]])
- }
- seg.slotCap *= 2
- seg.slotsData = newSlotData
- }
- func (seg *segment) updateEntryPtr(slotId uint8, hash16 uint16, oldOff, newOff int64) {
- slotOff := int32(slotId) * seg.slotCap
- slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match := seg.lookupByOff(slot, hash16, oldOff)
- if !match {
- return
- }
- ptr := &slot[idx]
- ptr.offset = newOff
- }
- func (seg *segment) insertEntryPtr(slotId uint8, hash16 uint16, offset int64, idx int, keyLen uint16) {
- slotOff := int32(slotId) * seg.slotCap
- if seg.slotLens[slotId] == seg.slotCap {
- seg.expand()
- slotOff *= 2
- }
- seg.slotLens[slotId]++
- seg.entryCount++
- slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- copy(slot[idx+1:], slot[idx:])
- slot[idx].offset = offset
- slot[idx].hash16 = hash16
- slot[idx].keyLen = keyLen
- }
- func (seg *segment) delEntryPtr(slotId uint8, hash16 uint16, offset int64) {
- slotOff := int32(slotId) * seg.slotCap
- slot := seg.slotsData[slotOff : slotOff+seg.slotLens[slotId] : slotOff+seg.slotCap]
- idx, match := seg.lookupByOff(slot, hash16, offset)
- if !match {
- return
- }
- var entryHdrBuf [ENTRY_HDR_SIZE]byte
- seg.rb.ReadAt(entryHdrBuf[:], offset)
- entryHdr := (*entryHdr)(unsafe.Pointer(&entryHdrBuf[0]))
- entryHdr.deleted = true
- seg.rb.WriteAt(entryHdrBuf[:], offset)
- copy(slot[idx:], slot[idx+1:])
- seg.slotLens[slotId]--
- seg.entryCount--
- }
- func entryPtrIdx(slot []entryPtr, hash16 uint16) (idx int) {
- high := len(slot)
- for idx < high {
- mid := (idx + high) >> 1
- oldEntry := &slot[mid]
- if oldEntry.hash16 < hash16 {
- idx = mid + 1
- } else {
- high = mid
- }
- }
- return
- }
- func (seg *segment) lookup(slot []entryPtr, hash16 uint16, key []byte) (idx int, match bool) {
- idx = entryPtrIdx(slot, hash16)
- for idx < len(slot) {
- ptr := &slot[idx]
- if ptr.hash16 != hash16 {
- break
- }
- match = int(ptr.keyLen) == len(key) && seg.rb.EqualAt(key, ptr.offset+ENTRY_HDR_SIZE)
- if match {
- return
- }
- idx++
- }
- return
- }
- func (seg *segment) lookupByOff(slot []entryPtr, hash16 uint16, offset int64) (idx int, match bool) {
- idx = entryPtrIdx(slot, hash16)
- for idx < len(slot) {
- ptr := &slot[idx]
- if ptr.hash16 != hash16 {
- break
- }
- match = ptr.offset == offset
- if match {
- return
- }
- idx++
- }
- return
- }
|