Skip to content

Commit 12bf1a0

Browse files
authored
nodelist (#187)
1 parent 9435f9a commit 12bf1a0

File tree

4 files changed

+295
-64
lines changed

4 files changed

+295
-64
lines changed

BitFaster.Caching/Lfu/ConcurrentLfu.cs

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ public class ConcurrentLfu<K, V> : ICache<K, V>, IBoundedPolicy
3737

3838
public const int BufferSize = 128;
3939

40-
private readonly ConcurrentDictionary<K, LinkedListNode<LfuNode<K, V>>> dictionary;
40+
private readonly ConcurrentDictionary<K, LfuNode<K, V>> dictionary;
4141

42-
private readonly StripedBuffer<LinkedListNode<LfuNode<K, V>>> readBuffer;
43-
private readonly StripedBuffer<LinkedListNode<LfuNode<K, V>>> writeBuffer;
42+
private readonly StripedBuffer<LfuNode<K, V>> readBuffer;
43+
private readonly StripedBuffer<LfuNode<K, V>> writeBuffer;
4444

4545
private readonly CacheMetrics metrics = new CacheMetrics();
4646

4747
private readonly CmSketch<K> cmSketch;
4848

49-
private readonly LinkedList<LfuNode<K, V>> windowLru;
50-
private readonly LinkedList<LfuNode<K, V>> probationLru;
51-
private readonly LinkedList<LfuNode<K, V>> protectedLru;
49+
private readonly LfuNodeList<K, V> windowLru;
50+
private readonly LfuNodeList<K, V> probationLru;
51+
private readonly LfuNodeList<K, V> protectedLru;
5252

5353
private readonly LfuCapacityPartition capacity;
5454

@@ -58,7 +58,7 @@ public class ConcurrentLfu<K, V> : ICache<K, V>, IBoundedPolicy
5858
private readonly IScheduler scheduler;
5959

6060
#if NETSTANDARD2_0
61-
private readonly LinkedListNode<LfuNode<K, V>>[] localDrainBuffer = new LinkedListNode<LfuNode<K, V>>[TakeBufferSize];
61+
private readonly LfuNode<K, V>[] localDrainBuffer = new LfuNode<K, V>[TakeBufferSize];
6262
#endif
6363

6464
public ConcurrentLfu(int capacity)
@@ -70,16 +70,16 @@ public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler)
7070
{
7171
var comparer = EqualityComparer<K>.Default;
7272

73-
this.dictionary = new ConcurrentDictionary<K, LinkedListNode<LfuNode<K, V>>>(concurrencyLevel, capacity, comparer);
73+
this.dictionary = new ConcurrentDictionary<K, LfuNode<K, V>>(concurrencyLevel, capacity, comparer);
7474

75-
this.readBuffer = new StripedBuffer<LinkedListNode<LfuNode<K, V>>>(concurrencyLevel, BufferSize);
76-
this.writeBuffer = new StripedBuffer<LinkedListNode<LfuNode<K, V>>>(concurrencyLevel, BufferSize);
75+
this.readBuffer = new StripedBuffer<LfuNode<K, V>>(concurrencyLevel, BufferSize);
76+
this.writeBuffer = new StripedBuffer<LfuNode<K, V>>(concurrencyLevel, BufferSize);
7777

7878
this.cmSketch = new CmSketch<K>(1, comparer);
7979
this.cmSketch.EnsureCapacity(capacity);
80-
this.windowLru = new LinkedList<LfuNode<K, V>>();
81-
this.probationLru = new LinkedList<LfuNode<K, V>>();
82-
this.protectedLru = new LinkedList<LfuNode<K, V>>();
80+
this.windowLru = new LfuNodeList<K, V>();
81+
this.probationLru = new LfuNodeList<K, V>();
82+
this.protectedLru = new LfuNodeList<K, V>();
8383

8484
this.capacity = new LfuCapacityPartition(capacity);
8585

@@ -109,7 +109,7 @@ public void AddOrUpdate(K key, V value)
109109
return;
110110
}
111111

112-
var node = new LinkedListNode<LfuNode<K, V>>(new LfuNode<K, V>(key, value));
112+
var node = new LfuNode<K, V>(key, value);
113113
if (this.dictionary.TryAdd(key, node))
114114
{
115115
AfterWrite(node);
@@ -133,7 +133,7 @@ public void Clear()
133133
public void Trim(int itemCount)
134134
{
135135
itemCount = Math.Min(itemCount, this.Count);
136-
var candidates = new List<LinkedListNode<LfuNode<K, V>>>(itemCount);
136+
var candidates = new List<LfuNode<K, V>>(itemCount);
137137

138138
// TODO: this is LRU order eviction, Caffeine void evictFromMain(int candidates) is based on frequency
139139
lock (maintenanceLock)
@@ -146,7 +146,7 @@ public void Trim(int itemCount)
146146

147147
foreach (var candidate in candidates)
148148
{
149-
this.TryRemove(candidate.Value.Key);
149+
this.TryRemove(candidate.Key);
150150
}
151151
}
152152

@@ -159,11 +159,11 @@ public V GetOrAdd(K key, Func<K, V> valueFactory)
159159
return value;
160160
}
161161

162-
var node = new LinkedListNode<LfuNode<K, V>>(new LfuNode<K, V>(key, valueFactory(key)));
162+
var node = new LfuNode<K, V>(key, valueFactory(key));
163163
if (this.dictionary.TryAdd(key, node))
164164
{
165165
AfterWrite(node);
166-
return node.Value.Value;
166+
return node.Value;
167167
}
168168
}
169169
}
@@ -178,7 +178,7 @@ public bool TryGet(K key, out V value)
178178
{
179179
TryScheduleDrain();
180180
}
181-
value = node.Value.Value;
181+
value = node.Value;
182182
return true;
183183
}
184184

@@ -192,7 +192,7 @@ public bool TryRemove(K key)
192192
{
193193
if (this.dictionary.TryRemove(key, out var node))
194194
{
195-
node.Value.WasRemoved = true;
195+
node.WasRemoved = true;
196196
AfterWrite(node);
197197
return true;
198198
}
@@ -204,7 +204,7 @@ public bool TryUpdate(K key, V value)
204204
{
205205
if (this.dictionary.TryGetValue(key, out var node))
206206
{
207-
node.Value.Value = value;
207+
node.Value = value;
208208

209209
// It's ok for this to be lossy, since the node is already tracked
210210
// and we will just lose ordering/hit count, but not orphan the node.
@@ -225,11 +225,11 @@ public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
225225
{
226226
foreach (var kvp in this.dictionary)
227227
{
228-
yield return new KeyValuePair<K, V>(kvp.Key, kvp.Value.Value.Value);
228+
yield return new KeyValuePair<K, V>(kvp.Key, kvp.Value.Value);
229229
}
230230
}
231231

232-
private static void TakeCandidatesInLruOrder(LinkedList<LfuNode<K, V>> lru, List<LinkedListNode<LfuNode<K, V>>> candidates, int itemCount)
232+
private static void TakeCandidatesInLruOrder(LfuNodeList<K, V> lru, List<LfuNode<K, V>> candidates, int itemCount)
233233
{
234234
var curr = lru.First;
235235

@@ -240,7 +240,7 @@ private static void TakeCandidatesInLruOrder(LinkedList<LfuNode<K, V>> lru, List
240240
}
241241
}
242242

243-
private void AfterWrite(LinkedListNode<LfuNode<K, V>> node)
243+
private void AfterWrite(LfuNode<K, V> node)
244244
{
245245
var spinner = new SpinWait();
246246

@@ -351,16 +351,14 @@ private void DrainBuffers()
351351
}
352352
}
353353

354-
const int takeBufferSize = 1024;
355-
356354
private bool Maintenance()
357355
{
358356
this.drainStatus.Set(DrainStatus.ProcessingToIdle);
359357

360358
bool wasDrained = false;
361359

362360
#if !NETSTANDARD2_0
363-
var localDrainBuffer = ArrayPool<LinkedListNode<LfuNode<K, V>>>.Shared.Rent(TakeBufferSize);
361+
var localDrainBuffer = ArrayPool<LfuNode<K, V>>.Shared.Rent(TakeBufferSize);
364362
#endif
365363
int maxSweeps = 1;
366364
int count = 0;
@@ -374,7 +372,7 @@ private bool Maintenance()
374372

375373
for (int i = 0; i < count; i++)
376374
{
377-
this.cmSketch.Increment(localDrainBuffer[i].Value.Key);
375+
this.cmSketch.Increment(localDrainBuffer[i].Key);
378376
}
379377

380378
for (int i = 0; i < count; i++)
@@ -393,7 +391,7 @@ private bool Maintenance()
393391
}
394392

395393
#if !NETSTANDARD2_0
396-
ArrayPool<LinkedListNode<LfuNode<K, V>>>.Shared.Return(localDrainBuffer);
394+
ArrayPool<LfuNode<K, V>>.Shared.Return(localDrainBuffer);
397395
#endif
398396

399397
// TODO: hill climb
@@ -411,18 +409,18 @@ private bool Maintenance()
411409
return wasDrained;
412410
}
413411

414-
private void OnAccess(LinkedListNode<LfuNode<K, V>> node)
412+
private void OnAccess(LfuNode<K, V> node)
415413
{
416414
// there was a cache hit even if the item was removed or is not yet added.
417415
this.metrics.requestHitCount++;
418416

419417
// Node is added to read buffer while it is removed by maintenance, or it is read before it has been added.
420-
if (node.List == null)
418+
if (node.list == null)
421419
{
422420
return;
423421
}
424422

425-
switch (node.Value.Position)
423+
switch (node.Position)
426424
{
427425
case Position.Window:
428426
this.windowLru.MoveToEnd(node);
@@ -436,27 +434,27 @@ private void OnAccess(LinkedListNode<LfuNode<K, V>> node)
436434
}
437435
}
438436

439-
private void OnWrite(LinkedListNode<LfuNode<K, V>> node)
437+
private void OnWrite(LfuNode<K, V> node)
440438
{
441439
// Nodes can be removed while they are in the write buffer, in which case they should
442440
// not be added back into the LRU.
443-
if (node.Value.WasRemoved)
441+
if (node.WasRemoved)
444442
{
445-
if (node.List != null)
443+
if (node.list != null)
446444
{
447-
node.List.Remove(node);
445+
node.list.Remove(node);
448446
}
449447

450448
return;
451449
}
452450

453-
this.cmSketch.Increment(node.Value.Key);
451+
this.cmSketch.Increment(node.Key);
454452

455453
// node can already be in one of the queues due to update
456-
switch (node.Value.Position)
454+
switch (node.Position)
457455
{
458456
case Position.Window:
459-
if (node.List == null)
457+
if (node.list == null)
460458
{
461459
this.windowLru.AddLast(node);
462460
TryEvict();
@@ -490,42 +488,42 @@ private void TryEvict()
490488
if (this.protectedLru.Count < capacity.Protected)
491489
{
492490
this.protectedLru.AddLast(candidate);
493-
candidate.Value.Position = Position.Protected;
491+
candidate.Position = Position.Protected;
494492
return;
495493
}
496494

497495
this.probationLru.AddLast(candidate);
498-
candidate.Value.Position = Position.Probation;
496+
candidate.Position = Position.Probation;
499497

500498
// remove either candidate or probation.first
501499
if (this.probationLru.Count > capacity.Probation)
502500
{
503-
var c = this.cmSketch.EstimateFrequency(candidate.Value.Key);
504-
var p = this.cmSketch.EstimateFrequency(this.probationLru.First.Value.Key);
501+
var c = this.cmSketch.EstimateFrequency(candidate.Key);
502+
var p = this.cmSketch.EstimateFrequency(this.probationLru.First.Key);
505503

506504
// TODO: random factor?
507505
var victim = (c > p) ? this.probationLru.First : candidate;
508506

509-
this.dictionary.TryRemove(victim.Value.Key, out var _);
510-
victim.List.Remove(victim);
507+
this.dictionary.TryRemove(victim.Key, out var _);
508+
victim.list.Remove(victim);
511509

512510
this.metrics.evictedCount++;
513511
}
514512
}
515513
}
516514

517-
private void PromoteProbation(LinkedListNode<LfuNode<K, V>> node)
515+
private void PromoteProbation(LfuNode<K, V> node)
518516
{
519517
this.probationLru.Remove(node);
520518
this.protectedLru.AddLast(node);
521-
node.Value.Position = Position.Protected;
519+
node.Position = Position.Protected;
522520

523521
if (this.protectedLru.Count > capacity.Protected)
524522
{
525523
var demoted = this.protectedLru.First;
526524
this.protectedLru.RemoveFirst();
527525

528-
demoted.Value.Position = Position.Probation;
526+
demoted.Position = Position.Probation;
529527
this.probationLru.AddLast(demoted);
530528
}
531529
}

BitFaster.Caching/Lfu/LfuNode.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ namespace BitFaster.Caching.Lfu
66
{
77
internal class LfuNode<K, V>
88
{
9+
internal LfuNodeList<K, V> list;
10+
internal LfuNode<K, V> next;
11+
internal LfuNode<K, V> prev;
12+
913
private volatile bool wasRemoved;
1014

1115
public LfuNode(K k, V v)
@@ -14,6 +18,13 @@ public LfuNode(K k, V v)
1418
this.Value = v;
1519
}
1620

21+
public LfuNode(LfuNodeList<K, V> list, K k, V v)
22+
{
23+
this.list = list;
24+
this.Key = k;
25+
this.Value = v;
26+
}
27+
1728
public readonly K Key;
1829

1930
public V Value { get; set; }
@@ -25,6 +36,18 @@ public bool WasRemoved
2536
get => this.wasRemoved;
2637
set => this.wasRemoved = value;
2738
}
39+
40+
public LfuNode<K, V> Next
41+
{
42+
get { return next == null || next == list.head ? null : next; }
43+
}
44+
45+
internal void Invalidate()
46+
{
47+
list = null;
48+
next = null;
49+
prev = null;
50+
}
2851
}
2952

3053
public enum Position

0 commit comments

Comments
 (0)