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  }