Update go-sieve-cache

This commit is contained in:
Frank Denis
2025-12-09 23:11:33 +01:00
parent e694da85d2
commit a8a481f8ed
5 changed files with 61 additions and 41 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ require (
github.com/jedisct1/go-hpke-compact v0.0.0-20241212093903-5caa4621366f
github.com/jedisct1/go-ipcrypt v0.1.1
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7
github.com/jedisct1/go-sieve-cache v0.1.7
github.com/jedisct1/go-sieve-cache v0.1.8
github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41
github.com/k-sone/critbitgo v1.4.0
github.com/kardianos/service v1.2.4
+2
View File
@@ -42,6 +42,8 @@ github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 h1:FWpSWRD8Fb
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7/go.mod h1:BMxO138bOokdgt4UaxZiEfypcSHX0t6SIFimVP1oRfk=
github.com/jedisct1/go-sieve-cache v0.1.7 h1:mdc4gTMZI6pUvMTze5K5CHxLxkk5iJcug85g2hERBVk=
github.com/jedisct1/go-sieve-cache v0.1.7/go.mod h1:EN88bnjKpiyS9TfZZNbUkCwgpWueZSaUi4vgVtw2988=
github.com/jedisct1/go-sieve-cache v0.1.8 h1:0/yr4dj2rI+u5XWSB4dyxCNeM8AX+vJdnH1mi/bOljc=
github.com/jedisct1/go-sieve-cache v0.1.8/go.mod h1:LV8jfNmswZpdMxHc2uu4R+gTe3kH8CGn/sKnKJVnank=
github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41 h1:TPF+VETyhqUOY51j3KF0uk5cgHQ2Bzi6XCorcGNGfTs=
github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41/go.mod h1:eh2PYNEklsNDqUxnbnN9Duvpw1b+ZectZAtDUDRj2tA=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+10 -13
View File
@@ -1,6 +1,7 @@
package sievecache
import (
"encoding/binary"
"errors"
"fmt"
"hash/maphash"
@@ -31,6 +32,12 @@ func NewShardedWithShards[K comparable, V any](capacity int, numShards int) (*Sh
return nil, errors.New("ShardedSieveCache: number of shards must be greater than 0")
}
// Avoid inflating total capacity: never create more shards than the
// total capacity can support (each shard needs at least 1 slot).
if numShards > capacity {
numShards = capacity
}
// Calculate per-shard capacity
baseCapacityPerShard := capacity / numShards
remaining := capacity % numShards
@@ -105,21 +112,11 @@ func (c *ShardedSieveCache[K, V]) getShardIndex(key K) int {
h.Write(k)
case int:
var buf [8]byte
buf[0] = byte(k)
buf[1] = byte(k >> 8)
buf[2] = byte(k >> 16)
buf[3] = byte(k >> 24)
h.Write(buf[:4])
binary.LittleEndian.PutUint64(buf[:], uint64(k))
h.Write(buf[:])
case int64:
var buf [8]byte
buf[0] = byte(k)
buf[1] = byte(k >> 8)
buf[2] = byte(k >> 16)
buf[3] = byte(k >> 24)
buf[4] = byte(k >> 32)
buf[5] = byte(k >> 40)
buf[6] = byte(k >> 48)
buf[7] = byte(k >> 56)
binary.LittleEndian.PutUint64(buf[:], uint64(k))
h.Write(buf[:])
default:
// For other types, convert to string
+47 -26
View File
@@ -104,7 +104,15 @@ func (c *SieveCache[K, V]) Insert(key K, value V) bool {
// Evict if at capacity
if len(c.nodes) >= c.capacity {
c.Evict()
_, evicted := c.Evict()
// When the cache is full and *all* entries are marked `visited`, our Evict() performs
// a first pass that clears the `visited` bits but may return false without removing
// anything. We still must free a slot before inserting, so we call Evict() a second
// time. This mirrors the original SIEVE miss path, which keeps scanning (wrapping once)
// until it finds an item to evict after clearing bits.
if !evicted {
c.Evict()
}
}
// Add new node to the end
@@ -136,16 +144,18 @@ func (c *SieveCache[K, V]) Remove(key K) (V, bool) {
return node.Value, true
}
// Update hand if needed
// Update hand if needed (scan direction is from low to high indices)
if c.handInitialized {
lastIdx := len(c.nodes) - 1
if c.hand == idx {
// Move hand to the previous node or wrap to end
if idx > 0 {
c.hand = idx - 1
} else {
c.hand = len(c.nodes) - 2
newLen := len(c.nodes) - 1
if newLen == 0 {
c.handInitialized = false
} else if idx >= newLen {
c.hand = 0
}
} else if c.hand == len(c.nodes)-1 {
// Otherwise keep hand at same position (swapped element will be there)
} else if c.hand == lastIdx {
// If hand points to the last element (which will be moved to idx)
c.hand = idx
}
@@ -182,12 +192,12 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
return zero, false
}
// Start from the hand pointer or the end if hand is not initialized
// Start from the hand pointer or index 0 (tail/oldest) if hand is not initialized
var currentIdx int
if c.handInitialized {
currentIdx = c.hand
} else {
currentIdx = len(c.nodes) - 1
currentIdx = 0
}
startIdx := currentIdx
@@ -195,7 +205,7 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
wrapped := false
foundIdx := -1
// Scan for a non-visited entry
// Scan for a non-visited entry (from tail toward head)
for {
// If current node is not visited, mark it for eviction
if !c.visited.Get(currentIdx) {
@@ -206,17 +216,17 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
// Mark as non-visited for next scan
c.visited.Set(currentIdx, false)
// Move to previous node or wrap to end
if currentIdx > 0 {
currentIdx--
// Move to next node (toward head) or wrap to beginning (tail)
if currentIdx+1 < len(c.nodes) {
currentIdx++
} else {
// Wrap around to end of slice
// Wrap around to beginning of slice
if wrapped {
// If we've already wrapped, break to avoid infinite loop
break
}
wrapped = true
currentIdx = len(c.nodes) - 1
currentIdx = 0
}
// If we've looped back to start, we've checked all nodes
@@ -228,17 +238,29 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
// If we found a node to evict
if foundIdx >= 0 {
evictIdx := foundIdx
lastIdx := len(c.nodes) - 1
// Update the hand pointer to the previous node or wrap to end
if evictIdx > 0 {
c.hand = evictIdx - 1
} else if len(c.nodes) > 1 {
c.hand = len(c.nodes) - 2
// Update the hand pointer to the next position in scan order
if evictIdx == lastIdx {
// Evicting the last element, hand wraps to 0
if len(c.nodes) > 1 {
c.hand = 0
c.handInitialized = true
} else {
c.handInitialized = false
}
} else {
// Keep hand at 0 but mark as not initialized
c.handInitialized = false
// swap_remove will move the last element to evictIdx
// Hand should point to evictIdx (where the moved element now is) to continue scanning
// But if hand would be >= new length, wrap to 0
newLen := len(c.nodes) - 1
if evictIdx < newLen {
c.hand = evictIdx
} else {
c.hand = 0
}
c.handInitialized = true
}
c.handInitialized = true
// Remove the key from the map
delete(c.indices, c.nodes[evictIdx].Key)
@@ -246,7 +268,7 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
// Remove the node and return its value
nodeToEvict := c.nodes[evictIdx]
if evictIdx == len(c.nodes)-1 {
if evictIdx == lastIdx {
// If last node, just remove it
c.nodes = c.nodes[:len(c.nodes)-1]
c.visited.Truncate(len(c.nodes))
@@ -254,7 +276,6 @@ func (c *SieveCache[K, V]) Evict() (V, bool) {
}
// Otherwise swap with the last node
lastIdx := len(c.nodes) - 1
lastNode := c.nodes[lastIdx]
c.nodes[evictIdx] = lastNode
c.visited.Set(evictIdx, c.visited.Get(lastIdx))
+1 -1
View File
@@ -53,7 +53,7 @@ github.com/jedisct1/go-ipcrypt
# github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7
## explicit; go 1.23.4
github.com/jedisct1/go-minisign
# github.com/jedisct1/go-sieve-cache v0.1.7
# github.com/jedisct1/go-sieve-cache v0.1.8
## explicit; go 1.21
github.com/jedisct1/go-sieve-cache/pkg/sievecache
# github.com/jedisct1/xsecretbox v0.0.0-20241212092125-3afc4917ac41