Skip to content

Commit f55bacb

Browse files
authored
unbounded (#370)
* unbounded * 128 * fix * const * comp * fix * comment * rem comment * rem dead code ---------
1 parent 7484d33 commit f55bacb

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,58 @@ public void WhenKeysAreContinuouslyRequestedInTheOrderTheyAreAddedCountIsBounded
348348
testOutputHelper.WriteLine($"Total: {lru.Count} Hot: {lru.HotCount} Warm: {lru.WarmCount} Cold: {lru.ColdCount}");
349349
lru.Count.Should().BeLessOrEqualTo(capacity + 1);
350350
}
351+
}
352+
353+
public class KeysInOrderTestDataGenerator : IEnumerable<object[]>
354+
{
355+
private readonly List<object[]> _data = new List<object[]>
356+
{
357+
new object[] { new EqualCapacityPartition(hotCap + warmCap + coldCap) },
358+
new object[] { new EqualCapacityPartition(128) },
359+
new object[] { new EqualCapacityPartition(256) },
360+
new object[] { new EqualCapacityPartition(1024) },
361+
new object[] { new FavorWarmPartition(128) },
362+
new object[] { new FavorWarmPartition(256) },
363+
new object[] { new FavorWarmPartition(1024) },
364+
new object[] { new FavorWarmPartition(128, 0.6) },
365+
new object[] { new FavorWarmPartition(256, 0.6) },
366+
new object[] { new FavorWarmPartition(1024, 0.6) },
367+
//new object[] { new FavorWarmPartition(10*1024, 0.6) },
368+
//new object[] { new FavorWarmPartition(100*1024, 0.6) },
369+
};
370+
371+
public IEnumerator<object[]> GetEnumerator() => _data.GetEnumerator();
372+
373+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
374+
}
375+
376+
[Theory]
377+
[ClassData(typeof(KeysInOrderTestDataGenerator))]
378+
public void WhenKeysAreContinuouslyRequestedInTheOrderTheyAreAddedCountIsBounded2(ICapacityPartition p)
379+
{
380+
int capacity = p.Hot + p.Cold + p.Warm;
381+
lru = new ConcurrentLru<int, string>(capacity, p, EqualityComparer<int>.Default);
382+
383+
testOutputHelper.WriteLine($"Capacity: {lru.Capacity} (Hot: {p.Hot} Warm: {p.Warm} Cold: {p.Cold})");
384+
385+
for (int i = 0; i < capacity + 10; i++)
386+
{
387+
lru.GetOrAdd(i, valueFactory.Create);
388+
389+
// Touch all items already cached in hot, warm and cold.
390+
// This is worst case scenario, since we touch them in the exact order they
391+
// were added.
392+
for (int j = 0; j < i; j++)
393+
{
394+
lru.GetOrAdd(j, valueFactory.Create);
395+
}
396+
}
397+
398+
// For larger cache sizes, I have observed capacity + 5. This is linked to the number of attempts.
399+
// This is clearly a bug that needs further investigation, but considered not harmful at this point
400+
// since growth is bounded, we just allow 4 more items than we should in the absolute worst case.
401+
testOutputHelper.WriteLine($"Total: {lru.Count} Hot: {lru.HotCount} Warm: {lru.WarmCount} Cold: {lru.ColdCount}");
402+
lru.Count.Should().BeLessOrEqualTo(capacity + 1);
351403
}
352404

353405
[Fact]

BitFaster.Caching/Lru/ConcurrentLruCore.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -541,13 +541,14 @@ private void Cycle(int hotCount)
541541
// Attempt to recover. It is possible that multiple threads read the same queue count here,
542542
// so this process has races that could reduce cache size below capacity. This manifests
543543
// in 'off by one' which is considered harmless.
544-
(dest, count) = CycleWarm(Volatile.Read(ref counter.warm));
544+
545+
(dest, count) = CycleCold(Volatile.Read(ref counter.cold));
545546
if (dest != ItemDestination.Remove)
546547
{
547548
continue;
548549
}
549550

550-
(dest, count) = CycleCold(Volatile.Read(ref counter.cold));
551+
(dest, count) = CycleWarm(Volatile.Read(ref counter.warm));
551552
if (dest != ItemDestination.Remove)
552553
{
553554
continue;
@@ -637,7 +638,7 @@ private void CycleDuringWarmup(int hotCount)
637638
[MethodImpl(MethodImplOptions.AggressiveInlining)]
638639
private (ItemDestination, int) CycleWarmUnchecked(ItemRemovedReason removedReason)
639640
{
640-
Interlocked.Decrement(ref this.counter.warm);
641+
int wc = Interlocked.Decrement(ref this.counter.warm);
641642

642643
if (this.warmQueue.TryDequeue(out var item))
643644
{
@@ -646,7 +647,7 @@ private void CycleDuringWarmup(int hotCount)
646647
// When the warm queue is full, we allow an overflow of 1 item before redirecting warm items to cold.
647648
// This only happens when hit rate is high, in which case we can consider all items relatively equal in
648649
// terms of which was least recently used.
649-
if (where == ItemDestination.Warm && Volatile.Read(ref this.counter.warm) <= this.capacity.Warm)
650+
if (where == ItemDestination.Warm && wc <= this.capacity.Warm)
650651
{
651652
return (ItemDestination.Warm, this.Move(item, where, removedReason));
652653
}

0 commit comments

Comments
 (0)