Skip to content

Commit 4b5c32d

Browse files
authored
perf (#174)
* proxy * tests * cleanup * cleanup * move trim expired
1 parent c526b8c commit 4b5c32d

File tree

4 files changed

+35
-21
lines changed

4 files changed

+35
-21
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,13 +227,13 @@ public void WhenRefToMetricsIsCapturedResultIsCorrect()
227227
[Fact]
228228
public void CanExpireIsFalse()
229229
{
230-
this.lru.CanExpire.Should().BeFalse();
230+
this.lru.Policy.ExpireAfterWrite.CanExpire.Should().BeFalse();
231231
}
232232

233233
[Fact]
234234
public void TimeToLiveIsInfinite()
235235
{
236-
this.lru.TimeToLive.Should().Be(NoneTimePolicy.Infinite);
236+
this.lru.Policy.ExpireAfterWrite.TimeToLive.Should().Be(NoneTimePolicy.Infinite);
237237
}
238238

239239
[Fact]

BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ public void ConstructPartitionCtorReturnsCapacity()
5555
[Fact]
5656
public void CanExpireIsTrue()
5757
{
58-
this.lru.CanExpire.Should().BeTrue();
58+
this.lru.Policy.ExpireAfterWrite.CanExpire.Should().BeTrue();
5959
}
6060

6161
[Fact]
6262
public void TimeToLiveIsCtorArg()
6363
{
64-
this.lru.TimeToLive.Should().Be(timeToLive);
64+
this.lru.Policy.ExpireAfterWrite.TimeToLive.Should().Be(timeToLive);
6565
}
6666

6767
[Fact]
@@ -155,7 +155,7 @@ public async Task WhenItemsAreExpiredExpireRemovesExpiredItems()
155155

156156
await Task.Delay(timeToLive * 2);
157157

158-
lru.TrimExpired();
158+
lru.Policy.ExpireAfterWrite.TrimExpired();
159159

160160
lru.Count.Should().Be(0);
161161
}
@@ -177,7 +177,7 @@ public async Task WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems(
177177
lru.GetOrAdd(2, valueFactory.Create);
178178
lru.GetOrAdd(3, valueFactory.Create);
179179

180-
lru.TrimExpired();
180+
lru.Policy.ExpireAfterWrite.TrimExpired();
181181

182182
lru.Count.Should().Be(3);
183183
}

BitFaster.Caching.UnitTests/Lru/FastConcurrentTLruTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task WhenItemsAreExpiredExpireRemovesExpiredItems()
4949

5050
await Task.Delay(ttl * 2);
5151

52-
lru.TrimExpired();
52+
lru.Policy.ExpireAfterWrite.TrimExpired();
5353

5454
lru.Count.Should().Be(0);
5555
}

BitFaster.Caching/Lru/ConcurrentLruCore.cs

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace BitFaster.Caching.Lru
2828
/// 5. When warm is full, warm tail is moved to warm head or cold depending on WasAccessed.
2929
/// 6. When cold is full, cold tail is moved to warm head or removed from dictionary on depending on WasAccessed.
3030
/// </remarks>
31-
public class ConcurrentLruCore<K, V, I, P, T> : ICache<K, V>, IAsyncCache<K, V>, IBoundedPolicy, ITimePolicy, IEnumerable<KeyValuePair<K, V>>
31+
public class ConcurrentLruCore<K, V, I, P, T> : ICache<K, V>, IAsyncCache<K, V>, IEnumerable<KeyValuePair<K, V>>
3232
where I : LruItem<K, V>
3333
where P : struct, IItemPolicy<K, V, I>
3434
where T : struct, ITelemetryPolicy<K, V>
@@ -53,8 +53,6 @@ public class ConcurrentLruCore<K, V, I, P, T> : ICache<K, V>, IAsyncCache<K, V>,
5353
// if mutate methods are called. Therefore, field must be mutable to maintain count.
5454
protected T telemetryPolicy;
5555

56-
private readonly CachePolicy policy;
57-
5856
public ConcurrentLruCore(
5957
int concurrencyLevel,
6058
ICapacityPartition capacity,
@@ -85,8 +83,6 @@ public ConcurrentLruCore(
8583
this.itemPolicy = itemPolicy;
8684
this.telemetryPolicy = telemetryPolicy;
8785
this.telemetryPolicy.SetEventSource(this);
88-
89-
this.policy = new CachePolicy(this, this);
9086
}
9187

9288
// No lock count: https://arbel.net/2013/02/03/best-practices-for-using-concurrentdictionary/
@@ -102,6 +98,8 @@ public ConcurrentLruCore(
10298
///<inheritdoc/>
10399
public ICacheEvents<K, V> Events => new Proxy(this);
104100

101+
public CachePolicy Policy => CreatePolicy(this);
102+
105103
public int HotCount => this.hotCount;
106104

107105
public int WarmCount => this.warmCount;
@@ -113,12 +111,6 @@ public ConcurrentLruCore(
113111
/// </summary>
114112
public ICollection<K> Keys => this.dictionary.Keys;
115113

116-
public CachePolicy Policy => this.policy;
117-
118-
public bool CanExpire => this.itemPolicy.CanDiscard();
119-
120-
public TimeSpan TimeToLive => this.itemPolicy.TimeToLive;
121-
122114
/// <summary>Returns an enumerator that iterates through the cache.</summary>
123115
/// <returns>An enumerator for the cache.</returns>
124116
/// <remarks>
@@ -350,11 +342,11 @@ public void Trim(int itemCount)
350342
TrimLiveItems(itemsRemoved, itemCount, capacity);
351343
}
352344

353-
public void TrimExpired()
345+
private void TrimExpired()
354346
{
355347
if (this.itemPolicy.CanDiscard())
356348
{
357-
TrimAllDiscardedItems();
349+
this.TrimAllDiscardedItems();
358350
}
359351
}
360352

@@ -631,14 +623,20 @@ IEnumerator IEnumerable.GetEnumerator()
631623
return ((ConcurrentLruCore<K, V, I, P, T>)this).GetEnumerator();
632624
}
633625

626+
private static CachePolicy CreatePolicy(ConcurrentLruCore<K, V, I, P, T> lru)
627+
{
628+
var p = new Proxy(lru);
629+
return new CachePolicy(p, p);
630+
}
631+
634632
// To get JIT optimizations, policies must be structs.
635633
// If the structs are returned directly via properties, they will be copied. Since
636634
// telemetryPolicy is a mutable struct, copy is bad. One workaround is to store the
637635
// state within the struct in an object. Since the struct points to the same object
638636
// it becomes immutable. However, this object is then somewhere else on the
639637
// heap, which slows down the policies with hit counter logic in benchmarks. Likely
640638
// this approach keeps the structs data members in the same CPU cache line as the LRU.
641-
private class Proxy : ICacheMetrics, ICacheEvents<K, V>
639+
private class Proxy : ICacheMetrics, ICacheEvents<K, V>, IBoundedPolicy, ITimePolicy
642640
{
643641
private readonly ConcurrentLruCore<K, V, I, P, T> lru;
644642

@@ -659,11 +657,27 @@ public Proxy(ConcurrentLruCore<K, V, I, P, T> lru)
659657

660658
public bool IsEnabled => (lru.telemetryPolicy as ICacheMetrics).IsEnabled;
661659

660+
public int Capacity => lru.Capacity;
661+
662+
public bool CanExpire => lru.itemPolicy.CanDiscard();
663+
664+
public TimeSpan TimeToLive => lru.itemPolicy.TimeToLive;
665+
662666
public event EventHandler<ItemRemovedEventArgs<K, V>> ItemRemoved
663667
{
664668
add { this.lru.telemetryPolicy.ItemRemoved += value; }
665669
remove { this.lru.telemetryPolicy.ItemRemoved -= value; }
666670
}
671+
672+
public void Trim(int itemCount)
673+
{
674+
lru.Trim(itemCount);
675+
}
676+
677+
public void TrimExpired()
678+
{
679+
lru.TrimExpired();
680+
}
667681
}
668682
}
669683
}

0 commit comments

Comments
 (0)