@@ -179,14 +179,11 @@ func newS3FIFO[K comparable, V any](cfg *config) *s3fifo[K, V] {
179179 capacity = 16384 // 2^14, divides evenly by 16 shards
180180 }
181181
182- // Calculate number of shards using tiered approach:
183- // - Small caches (<64K): prioritize concurrency with smaller shards (256 entries min)
184- // - Large caches (>=64K): prioritize S3-FIFO effectiveness (4096 entries min)
185- // Round down to nearest power of 2 for fast modulo via bitwise AND
186- minEntriesPerShard := 4096
187- if capacity < 65536 {
188- minEntriesPerShard = 256
189- }
182+ // Use 1024 min entries per shard to balance concurrency with hash variance.
183+ // With fewer entries per shard, statistical variance causes capacity loss
184+ // (e.g., 256/shard loses ~2%, 1024/shard loses ~0.6%).
185+ // Round down to nearest power of 2 for fast modulo via bitwise AND.
186+ minEntriesPerShard := 1024
190187 numShards := capacity / minEntriesPerShard
191188 if numShards < 1 {
192189 numShards = 1
@@ -220,15 +217,11 @@ func newS3FIFO[K comparable, V any](cfg *config) *s3fifo[K, V] {
220217 c .keyIsString = true
221218 }
222219
223- // Auto-tune ratios based on capacity.
224220 // S3-FIFO paper recommends small queue at 10% of total capacity.
225- // We use a small scaling factor for larger caches (up to 20%) .
221+ // Ghost queue at 19% provides optimal balance for varied workloads .
226222 var smallRatio , ghostRatio float64
227- smallRatio = 0.10 + 0.10 * (float64 (capacity )/ 250000.0 )
228- if smallRatio > 0.20 {
229- smallRatio = 0.20
230- }
231- ghostRatio = 2.5 // Constant 250% ghost tracking
223+ smallRatio = 0.10
224+ ghostRatio = 0.19
232225
233226 // Prepare hasher for Bloom filter
234227 var hasher func (K ) uint64
@@ -534,36 +527,32 @@ func (s *shard[K, V]) delete(key K) {
534527}
535528
536529// evictFromSmall evicts an entry from the small queue.
530+ // Items accessed more than once (freq > 1) are promoted to Main,
531+ // items with freq <= 1 are evicted to ghost queue.
537532func (s * shard [K , V ]) evictFromSmall () {
538- // Adaptive threshold: Small caches need stricter admission (require more hits)
539- // Large caches can afford to admit items with fewer hits.
540- threshold := int32 (1 )
541- if s .capacity < 100000 {
542- threshold = 2
543- }
544-
545533 for s .small .len > 0 {
546534 ent := s .small .front ()
547535 s .small .remove (ent )
548536
549- // Check if accessed since last eviction attempt
550- if ent .freq .Load () <= threshold {
537+ // Check if accessed more than once (freq > 1 to promote)
538+ if ent .freq .Load () <= 1 {
551539 // Not accessed enough - evict and track in ghost
552540 delete (s .entries , ent .key )
553541 s .addToGhost (ent .key )
554542 s .putEntry (ent )
555543 return
556544 }
557545
558- // Accessed - promote to Main queue
559- // Reset frequency per S3-FIFO paper : entry must prove itself in Main
546+ // Accessed more than once - promote to Main queue
547+ // Reset frequency: entry must prove itself in Main
560548 ent .freq .Store (0 )
561549 ent .inSmall = false
562550 s .main .pushBack (ent )
563551 }
564552}
565553
566554// evictFromMain evicts an entry from the main queue.
555+ // Per S3-FIFO paper: evicted items from Main are NOT added to ghost queue.
567556func (s * shard [K , V ]) evictFromMain () {
568557 for s .main .len > 0 {
569558 ent := s .main .front ()
@@ -572,9 +561,8 @@ func (s *shard[K, V]) evictFromMain() {
572561 // Check if accessed since last eviction attempt
573562 f := ent .freq .Load ()
574563 if f == 0 {
575- // Not accessed - evict
564+ // Not accessed - evict (no ghost tracking per S3-FIFO)
576565 delete (s .entries , ent .key )
577- s .addToGhost (ent .key )
578566 s .putEntry (ent )
579567 return
580568 }
0 commit comments