Skip to content

Commit a99aa78

Browse files
authored
optimize LFU (#438)
* test * simplify+inline * rem del * inline iter * match caffeine * afterwrite ---------
1 parent d3a781c commit a99aa78

File tree

2 files changed

+41
-20
lines changed

2 files changed

+41
-20
lines changed

BitFaster.Caching/Buffers/MpscBoundedBuffer.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private int GetCount(int head, int tail)
8686
public BufferStatus TryAdd(T item)
8787
{
8888
int head = Volatile.Read(ref headAndTail.Head);
89-
int tail = Volatile.Read(ref headAndTail.Tail);
89+
int tail = headAndTail.Tail;
9090
int size = tail - head;
9191

9292
if (size >= buffer.Length)
@@ -117,7 +117,7 @@ public BufferStatus TryAdd(T item)
117117
public BufferStatus TryTake(out T item)
118118
{
119119
int head = Volatile.Read(ref headAndTail.Head);
120-
int tail = Volatile.Read(ref headAndTail.Tail);
120+
int tail = headAndTail.Tail;
121121
int size = tail - head;
122122

123123
if (size == 0)
@@ -136,7 +136,7 @@ public BufferStatus TryTake(out T item)
136136
return BufferStatus.Contended;
137137
}
138138

139-
Volatile.Write(ref buffer[index], null);
139+
buffer[index] = null;
140140
Volatile.Write(ref this.headAndTail.Head, ++head);
141141
return BufferStatus.Success;
142142
}
@@ -190,7 +190,7 @@ private int DrainToImpl(Span<T> output)
190190
#endif
191191
{
192192
int head = Volatile.Read(ref headAndTail.Head);
193-
int tail = Volatile.Read(ref headAndTail.Tail);
193+
int tail = headAndTail.Tail;
194194
int size = tail - head;
195195

196196
if (size == 0)
@@ -214,13 +214,13 @@ private int DrainToImpl(Span<T> output)
214214
break;
215215
}
216216

217-
Volatile.Write(ref localBuffer[index], null);
217+
localBuffer[index] = null;
218218
Write(output, outCount++, item);
219219
head++;
220220
}
221221
while (head != tail && outCount < Length(output));
222222

223-
Volatile.Write(ref this.headAndTail.Head, head);
223+
this.headAndTail.Head = head;
224224

225225
return outCount;
226226
}

BitFaster.Caching/Lfu/ConcurrentLfu.cs

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
77
using System.Linq;
8+
using System.Runtime.CompilerServices;
89
using System.Runtime.InteropServices;
910
using System.Threading;
1011
using System.Threading.Tasks;
@@ -70,7 +71,7 @@ public sealed class ConcurrentLfu<K, V> : ICache<K, V>, IAsyncCache<K, V>, IBoun
7071

7172
private readonly IScheduler scheduler;
7273

73-
private readonly LfuNode<K, V>[] drainBuffer;
74+
private readonly LfuNode<K, V>[] drainBuffer;
7475

7576
/// <summary>
7677
/// Initializes a new instance of the ConcurrentLfu class with the specified capacity.
@@ -478,9 +479,10 @@ private void AfterWrite(LfuNode<K, V> node)
478479
private void ScheduleAfterWrite()
479480
{
480481
var spinner = new SpinWait();
482+
int status = this.drainStatus.NonVolatileRead();
481483
while (true)
482484
{
483-
switch (this.drainStatus.Status())
485+
switch (status)
484486
{
485487
case DrainStatus.Idle:
486488
this.drainStatus.Cas(DrainStatus.Idle, DrainStatus.Required);
@@ -494,6 +496,7 @@ private void ScheduleAfterWrite()
494496
{
495497
return;
496498
}
499+
status = this.drainStatus.VolatileRead();
497500
break;
498501
case DrainStatus.ProcessingToRequired:
499502
return;
@@ -509,8 +512,8 @@ IEnumerator IEnumerable.GetEnumerator()
509512

510513
private void TryScheduleDrain()
511514
{
512-
if (this.drainStatus.Status() >= DrainStatus.ProcessingToIdle)
513-
{
515+
if (this.drainStatus.NonVolatileRead() >= DrainStatus.ProcessingToIdle)
516+
{
514517
return;
515518
}
516519

@@ -521,15 +524,15 @@ private void TryScheduleDrain()
521524

522525
if (lockTaken)
523526
{
524-
int status = this.drainStatus.Status();
527+
int status = this.drainStatus.NonVolatileRead();
525528

526529
if (status >= DrainStatus.ProcessingToIdle)
527530
{
528531
return;
529532
}
530533

531-
this.drainStatus.Set(DrainStatus.ProcessingToIdle);
532-
scheduler.Run(() => DrainBuffers());
534+
this.drainStatus.VolatileWrite(DrainStatus.ProcessingToIdle);
535+
scheduler.Run(() => this.DrainBuffers());
533536
}
534537
}
535538
finally
@@ -559,15 +562,15 @@ private void DrainBuffers()
559562
}
560563
}
561564

562-
if (this.drainStatus.Status() == DrainStatus.Required)
565+
if (this.drainStatus.VolatileRead() == DrainStatus.Required)
563566
{
564567
TryScheduleDrain();
565568
}
566569
}
567570

568571
private bool Maintenance(LfuNode<K, V> droppedWrite = null)
569572
{
570-
this.drainStatus.Set(DrainStatus.ProcessingToIdle);
573+
this.drainStatus.VolatileWrite(DrainStatus.ProcessingToIdle);
571574

572575
// Note: this is only Span on .NET Core 3.1+, else this is no-op and it is still an array
573576
var buffer = this.drainBuffer.AsSpanOrArray();
@@ -609,10 +612,10 @@ private bool Maintenance(LfuNode<K, V> droppedWrite = null)
609612
// 1. We drained both input buffers (all work done)
610613
// 2. or scheduler is foreground (since don't run continuously on the foreground)
611614
if ((done || !scheduler.IsBackground) &&
612-
(this.drainStatus.Status() != DrainStatus.ProcessingToIdle ||
615+
(this.drainStatus.NonVolatileRead() != DrainStatus.ProcessingToIdle ||
613616
!this.drainStatus.Cas(DrainStatus.ProcessingToIdle, DrainStatus.Idle)))
614617
{
615-
this.drainStatus.Set(DrainStatus.Required);
618+
this.drainStatus.NonVolatileWrite(DrainStatus.Required);
616619
}
617620

618621
return done;
@@ -743,13 +746,15 @@ private ref struct EvictIterator
743746
public LfuNode<K, V> node;
744747
public int freq;
745748

749+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
746750
public EvictIterator(CmSketch<K> sketch, LfuNode<K, V> node)
747751
{
748752
this.sketch = sketch;
749753
this.node = node;
750754
freq = node == null ? -1 : sketch.EstimateFrequency(node.Key);
751755
}
752756

757+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
753758
public void Next()
754759
{
755760
node = node.Next;
@@ -863,9 +868,10 @@ private class DrainStatus
863868

864869
private PaddedInt drainStatus; // mutable struct, don't mark readonly
865870

871+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
866872
public bool ShouldDrain(bool delayable)
867873
{
868-
int status = Volatile.Read(ref this.drainStatus.Value);
874+
int status = this.NonVolatileRead();
869875
return status switch
870876
{
871877
Idle => !delayable,
@@ -875,19 +881,34 @@ public bool ShouldDrain(bool delayable)
875881
};
876882
}
877883

878-
public void Set(int newStatus)
884+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
885+
public void VolatileWrite(int newStatus)
879886
{
880887
Volatile.Write(ref this.drainStatus.Value, newStatus);
888+
}
889+
890+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
891+
public void NonVolatileWrite(int newStatus)
892+
{
893+
this.drainStatus.Value = newStatus;
881894
}
882895

896+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
883897
public bool Cas(int oldStatus, int newStatus)
884898
{
885899
return Interlocked.CompareExchange(ref this.drainStatus.Value, newStatus, oldStatus) == oldStatus;
886900
}
887901

888-
public int Status()
902+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
903+
public int VolatileRead()
889904
{
890905
return Volatile.Read(ref this.drainStatus.Value);
906+
}
907+
908+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
909+
public int NonVolatileRead()
910+
{
911+
return this.drainStatus.Value;
891912
}
892913

893914
[ExcludeFromCodeCoverage]

0 commit comments

Comments
 (0)