github.com/phuslu/lru@v1.0.16-0.20240421170520-46288a2fd47c/lru_shard.go (about)

     1  // Copyright 2023-2024 Phus Lu. All rights reserved.
     2  
     3  package lru
     4  
     5  import (
     6  	"sync"
     7  	"unsafe"
     8  )
     9  
    10  // lrunode is a list of lru node, storing key-value pairs and related information
    11  type lrunode[K comparable, V any] struct {
    12  	key   K
    13  	next  uint32
    14  	prev  uint32
    15  	value V
    16  }
    17  
    18  type lrubucket struct {
    19  	hdib  uint32 // bitfield { hash:24 dib:8 }
    20  	index uint32 // node index
    21  }
    22  
    23  // lrushard is a LRU partition contains a list and a hash table.
    24  type lrushard[K comparable, V any] struct {
    25  	mu sync.Mutex
    26  
    27  	// the hash table, with 20% extra space than the list for fewer conflicts.
    28  	table_buckets []uint64 // []lrubucket
    29  	table_mask    uint32
    30  	table_length  uint32
    31  	table_hasher  func(key unsafe.Pointer, seed uintptr) uintptr
    32  	table_seed    uintptr
    33  
    34  	// the list of nodes
    35  	list []lrunode[K, V]
    36  
    37  	// stats
    38  	stats_getcalls uint64
    39  	stats_setcalls uint64
    40  	stats_misses   uint64
    41  
    42  	// padding
    43  	_ [24]byte
    44  }
    45  
    46  func (s *lrushard[K, V]) Init(size uint32, hasher func(key unsafe.Pointer, seed uintptr) uintptr, seed uintptr) {
    47  	s.list_Init(size)
    48  	s.table_Init(size, hasher, seed)
    49  }
    50  
    51  func (s *lrushard[K, V]) Get(hash uint32, key K) (value V, ok bool) {
    52  	s.mu.Lock()
    53  
    54  	s.stats_getcalls++
    55  
    56  	if index, exists := s.table_Get(hash, key); exists {
    57  		s.list_MoveToFront(index)
    58  		// value = s.list[index].value
    59  		value = (*lrunode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0]))).value
    60  		ok = true
    61  	} else {
    62  		s.stats_misses++
    63  	}
    64  
    65  	s.mu.Unlock()
    66  
    67  	return
    68  }
    69  
    70  func (s *lrushard[K, V]) Peek(hash uint32, key K) (value V, ok bool) {
    71  	s.mu.Lock()
    72  
    73  	if index, exists := s.table_Get(hash, key); exists {
    74  		value = s.list[index].value
    75  		ok = true
    76  	}
    77  
    78  	s.mu.Unlock()
    79  
    80  	return
    81  }
    82  
    83  func (s *lrushard[K, V]) SetIfAbsent(hash uint32, key K, value V) (prev V, replaced bool) {
    84  	s.mu.Lock()
    85  
    86  	if index, exists := s.table_Get(hash, key); exists {
    87  		prev = s.list[index].value
    88  		s.mu.Unlock()
    89  		return
    90  	}
    91  
    92  	s.stats_setcalls++
    93  
    94  	// index := s.list_Back()
    95  	// node := &s.list[index]
    96  	index := s.list[0].prev
    97  	node := (*lrunode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
    98  	evictedValue := node.value
    99  	s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key)
   100  
   101  	node.key = key
   102  	node.value = value
   103  	s.table_Set(hash, key, index)
   104  	s.list_MoveToFront(index)
   105  	prev = evictedValue
   106  
   107  	s.mu.Unlock()
   108  	return
   109  }
   110  
   111  func (s *lrushard[K, V]) Set(hash uint32, key K, value V) (prev V, replaced bool) {
   112  	s.mu.Lock()
   113  
   114  	s.stats_setcalls++
   115  
   116  	if index, exists := s.table_Get(hash, key); exists {
   117  		// node := &s.list[index]
   118  		node := (*lrunode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   119  		previousValue := node.value
   120  		s.list_MoveToFront(index)
   121  		node.value = value
   122  		prev = previousValue
   123  		replaced = true
   124  
   125  		s.mu.Unlock()
   126  		return
   127  	}
   128  
   129  	// index := s.list_Back()
   130  	// node := &s.list[index]
   131  	index := s.list[0].prev
   132  	node := (*lrunode[K, V])(unsafe.Add(unsafe.Pointer(&s.list[0]), uintptr(index)*unsafe.Sizeof(s.list[0])))
   133  	evictedValue := node.value
   134  	if key != node.key {
   135  		s.table_Delete(uint32(s.table_hasher(noescape(unsafe.Pointer(&node.key)), s.table_seed)), node.key)
   136  	}
   137  
   138  	node.key = key
   139  	node.value = value
   140  	s.table_Set(hash, key, index)
   141  	s.list_MoveToFront(index)
   142  	prev = evictedValue
   143  
   144  	s.mu.Unlock()
   145  	return
   146  }
   147  
   148  func (s *lrushard[K, V]) Delete(hash uint32, key K) (v V) {
   149  	s.mu.Lock()
   150  
   151  	if index, exists := s.table_Get(hash, key); exists {
   152  		node := &s.list[index]
   153  		value := node.value
   154  		s.list_MoveToBack(index)
   155  		node.value = v
   156  		s.table_Delete(hash, key)
   157  		v = value
   158  	}
   159  
   160  	s.mu.Unlock()
   161  
   162  	return
   163  }
   164  
   165  func (s *lrushard[K, V]) Len() (n uint32) {
   166  	s.mu.Lock()
   167  	// inlining s.table_Len()
   168  	n = s.table_length
   169  	s.mu.Unlock()
   170  
   171  	return
   172  }
   173  
   174  func (s *lrushard[K, V]) AppendKeys(dst []K) []K {
   175  	s.mu.Lock()
   176  	for _, bucket := range s.table_buckets {
   177  		b := (*lrubucket)(unsafe.Pointer(&bucket))
   178  		if b.index == 0 {
   179  			continue
   180  		}
   181  		dst = append(dst, s.list[b.index].key)
   182  	}
   183  	s.mu.Unlock()
   184  
   185  	return dst
   186  }