From f91e13c2c020d67830d4fd2f2219ac9e77bd154a Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 12 Jul 2022 20:13:37 -0700 Subject: [PATCH 1/5] warmup --- .../Lru/TemplateConcurrentLru.cs | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/BitFaster.Caching/Lru/TemplateConcurrentLru.cs b/BitFaster.Caching/Lru/TemplateConcurrentLru.cs index 5e4488b3..16024f24 100644 --- a/BitFaster.Caching/Lru/TemplateConcurrentLru.cs +++ b/BitFaster.Caching/Lru/TemplateConcurrentLru.cs @@ -47,6 +47,7 @@ public class TemplateConcurrentLru : ICache, IEnumerable this.capacity.Hot) + { + Interlocked.Decrement(ref this.hotCount); + + if (this.hotQueue.TryDequeue(out var item)) + { + // always move to warm until it is full + if (this.warmCount < this.capacity.Warm) + { + // If there is a race, we will potentially add multiple items to warm. Guard by cycling the queue. + this.Move(item, ItemDestination.Warm, ItemRemovedReason.Evicted); + CycleWarm(); + } + else + { + // Else mark isWarm and move items to cold. + // If there is a race, we will potentially add multiple items to cold. Guard by cycling the queue. + Volatile.Write(ref this.isWarm, true); + this.Move(item, ItemDestination.Cold, ItemRemovedReason.Evicted); + CycleCold(); + } + } + else + { + Interlocked.Increment(ref this.hotCount); + } + } } private void CycleHot() From 25619c8fa47d83ed5018b6ba20c71905fe95bbba Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Jul 2022 16:16:51 -0700 Subject: [PATCH 2/5] fix unit tests --- .../Arc/Analysis.cs | 13 +- .../Arc/ArcDataFile.cs | 38 ++- .../Arc/Runner.cs | 15 +- .../ReadOnlySpanExtensions.cs | 96 ++++++++ .../Lru/ConcurrentLruTests.cs | 223 ++++++++++++------ .../Lru/ConcurrentTLruTests.cs | 14 +- 6 files changed, 311 insertions(+), 88 deletions(-) create mode 100644 BitFaster.Caching.HitRateAnalysis/ReadOnlySpanExtensions.cs diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs b/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs index 4727fbd6..cac6079a 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs @@ -13,13 +13,14 @@ namespace BitFaster.Caching.HitRateAnalysis.Arc { public class Analysis { - private readonly ConcurrentLru concurrentLru; - private readonly ClassicLru classicLru; + private readonly ConcurrentLru concurrentLru; + private readonly ClassicLru classicLru; + private static readonly object dummy = new object(); public Analysis(int cacheSize) { - concurrentLru = new ConcurrentLru(1, cacheSize, EqualityComparer.Default); - classicLru = new ClassicLru(1, cacheSize, EqualityComparer.Default); + concurrentLru = new ConcurrentLru(1, cacheSize, EqualityComparer.Default); + classicLru = new ClassicLru(1, cacheSize, EqualityComparer.Default); } public int CacheSize => concurrentLru.Capacity; @@ -30,8 +31,8 @@ public Analysis(int cacheSize) public void TestKey(long key) { - concurrentLru.GetOrAdd(key, u => 1); - classicLru.GetOrAdd(key, u => 1); + concurrentLru.GetOrAdd(key, u => dummy); + classicLru.GetOrAdd(key, u => dummy); } public static void WriteToFile(string path, IEnumerable results) diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs b/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs index 4afaae48..7066bcab 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs @@ -68,7 +68,7 @@ public async Task DownloadIfNotExistsAsync() } } - public IEnumerable EnumerateFileData() + public IEnumerable EnumerateFileData() { // File Format: // Every line in every file has four fields. @@ -98,19 +98,45 @@ public IEnumerable EnumerateFileData() while (sr.Peek() >= 0) { var line = sr.ReadLine(); - var chunks = line.Split(' '); - if (long.TryParse(chunks[0], out var startBlock)) + ReadOnlySpan buffer = line.AsSpan(); + + var chunks = buffer.Split(' '); + + if (chunks.MoveNext()) { - if (int.TryParse(chunks[1], out var sequence)) + if (long.TryParse(buffer[chunks.Current], out long startBlock)) { - for (long i = startBlock; i < startBlock + sequence; i++) + if (chunks.MoveNext()) { - yield return i; + if (int.TryParse(buffer[chunks.Current], out var sequence)) + { + yield return new BlockRange(startBlock, sequence); + + //for (long i = startBlock; i < startBlock + sequence; i++) + //{ + // yield return i; + //} + } } } } } } } + + public class BlockRange + { + private readonly long start; + private int sequence; + + public BlockRange(long start, int sequence) + { + this.start = start; + this.sequence = sequence; + } + + public long Start => this.start; + public int Sequence => this.sequence; + } } diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs b/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs index b44f8b75..6acb729a 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs @@ -23,22 +23,29 @@ public async Task Run() Console.WriteLine("Running..."); int count = 0; + int keys = 0; var sw = Stopwatch.StartNew(); - foreach (var key in this.config.File.EnumerateFileData()) + foreach (var block in this.config.File.EnumerateFileData()) { foreach (var a in this.config.Analysis) { - a.TestKey(key); + for (long i = block.Start; i < block.Start + block.Sequence; i++) + { + a.TestKey(i); + } + + } + keys += block.Sequence; if (++count % 100000 == 0) { - Console.WriteLine($"Processed {count} keys..."); + Console.WriteLine($"Processed {keys} keys..."); } } - Console.WriteLine($"Tested {count} keys in {sw.Elapsed}"); + Console.WriteLine($"Tested {keys} keys in {sw.Elapsed}"); this.config.Analysis.WriteToConsole(); Analysis.WriteToFile(this.config.Name, this.config.Analysis); diff --git a/BitFaster.Caching.HitRateAnalysis/ReadOnlySpanExtensions.cs b/BitFaster.Caching.HitRateAnalysis/ReadOnlySpanExtensions.cs new file mode 100644 index 00000000..03a372f4 --- /dev/null +++ b/BitFaster.Caching.HitRateAnalysis/ReadOnlySpanExtensions.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BitFaster.Caching.HitRateAnalysis +{ + // This was added in .NET 5 then reverted since it only splits on space + // https://github.com/dotnet/runtime/pull/295/files + public static class ReadOnlySpanExtensions + { + public static SpanSplitEnumerator Split(this ReadOnlySpan span) => new SpanSplitEnumerator(span, ' '); + + public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) => new SpanSplitEnumerator(span, separator); + + public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) => new SpanSplitEnumerator(span, separator ?? string.Empty); + } + + public ref struct SpanSplitEnumerator where T : IEquatable + { + private readonly ReadOnlySpan _buffer; + + private readonly ReadOnlySpan _separators; + private readonly T _separator; + + private readonly int _separatorLength; + private readonly bool _splitOnSingleToken; + + private readonly bool _isInitialized; + + private int _startCurrent; + private int _endCurrent; + private int _startNext; + + /// + /// Returns an enumerator that allows for iteration over the split span. + /// + /// Returns a that can be used to iterate over the split span. + public SpanSplitEnumerator GetEnumerator() => this; + + /// + /// Returns the current element of the enumeration. + /// + /// Returns a instance that indicates the bounds of the current element withing the source span. + public Range Current => new Range(_startCurrent, _endCurrent); + + internal SpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separators) + { + _isInitialized = true; + _buffer = span; + _separators = separators; + _separator = default!; + _splitOnSingleToken = false; + _separatorLength = _separators.Length != 0 ? _separators.Length : 1; + _startCurrent = 0; + _endCurrent = 0; + _startNext = 0; + } + + internal SpanSplitEnumerator(ReadOnlySpan span, T separator) + { + _isInitialized = true; + _buffer = span; + _separator = separator; + _separators = default; + _splitOnSingleToken = true; + _separatorLength = 1; + _startCurrent = 0; + _endCurrent = 0; + _startNext = 0; + } + + /// + /// Advances the enumerator to the next element of the enumeration. + /// + /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the enumeration. + public bool MoveNext() + { + if (!_isInitialized || _startNext > _buffer.Length) + { + return false; + } + + ReadOnlySpan slice = _buffer.Slice(_startNext); + _startCurrent = _startNext; + + int separatorIndex = _splitOnSingleToken ? slice.IndexOf(_separator) : slice.IndexOf(_separators); + int elementLength = (separatorIndex != -1 ? separatorIndex : slice.Length); + + _endCurrent = _startCurrent + elementLength; + _startNext = _endCurrent + _separatorLength; + return true; + } + } +} diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs index e2981b59..24dfc07a 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs @@ -150,6 +150,14 @@ public void WhenItemsAddedEnumerateContainsKvps() enumerable.Should().BeEquivalentTo(new[] { new KeyValuePair(1, "1"), new KeyValuePair(2, "2") }); } + [Fact] + public void FromColdWarmupFillsWarmQueue() + { + this.Warmup(); + + this.lru.Count.Should().Be(9); + } + [Fact] public void WhenItemExistsTryGetReturnsValueAndTrue() { @@ -226,14 +234,12 @@ public async Task WhenDifferentKeysAreRequesteValueIsCreatedForEachAsync() [Fact] public void WhenValuesAreNotReadAndMoreKeysRequestedThanCapacityCountDoesNotIncrease() { - int hotColdCapacity = hotCap + coldCap; - for (int i = 0; i < hotColdCapacity + 1; i++) - { - lru.GetOrAdd(i, valueFactory.Create); - } + this.Warmup(); + + var result = lru.GetOrAdd(1, valueFactory.Create); - lru.Count.Should().Be(hotColdCapacity); - valueFactory.timesCalled.Should().Be(hotColdCapacity + 1); + lru.Count.Should().Be(9); + valueFactory.timesCalled.Should().Be(10); } [Fact] @@ -276,121 +282,179 @@ public void WhenKeysAreContinuouslyRequestedInTheOrderTheyAreAddedCountIsBounded } } + // [Theory] + // [InlineData(new[] { 9 }, new[] { 9, 8, 7, 3, 2, 1, 6, 5 })] + //[InlineData(2, new[] { 9, 8, 7, 3, 2, 1, 6 })] + //[InlineData(3, new[] { 9, 8, 7, 3, 2, 1 })] + //[InlineData(4, new[] { 9, 8, 7, 3, 2 })] + //[InlineData(5, new[] { 9, 8, 7, 3 })] + //[InlineData(6, new[] { 9, 8, 7 })] + //[InlineData(7, new[] { 9, 8 })] + //[InlineData(8, new[] { 9 })] + //[InlineData(9, new int[] { })] + //public void EvictionFlow(int[] input, int[] expected) + //{ + // this.Warmup(); + + // foreach (var k in input) + // { + // this.lru.GetOrAdd(k, valueFactory.Create); + // } + + // lru.Keys.Should().BeEquivalentTo(expected); + //} + [Fact] public void WhenValueIsNotTouchedAndExpiresFromHotValueIsBumpedToCold() { - lru.GetOrAdd(0, valueFactory.Create); + this.Warmup(); + + lru.GetOrAdd(0, valueFactory.Create); // Don't touch in hot + lru.GetOrAdd(1, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); lru.GetOrAdd(3, valueFactory.Create); + lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(5, valueFactory.Create); + lru.GetOrAdd(6, valueFactory.Create); + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(0); - lru.ColdCount.Should().Be(1); + lru.TryGet(0, out var value).Should().BeFalse(); } [Fact] public void WhenValueIsTouchedAndExpiresFromHotValueIsBumpedToWarm() { + this.Warmup(); + lru.GetOrAdd(0, valueFactory.Create); - lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(0, valueFactory.Create); // Touch in hot lru.GetOrAdd(1, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); lru.GetOrAdd(3, valueFactory.Create); + lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(5, valueFactory.Create); + lru.GetOrAdd(6, valueFactory.Create); + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(1); - lru.ColdCount.Should().Be(0); + lru.TryGet(0, out var value).Should().BeTrue(); } [Fact] public void WhenValueIsTouchedAndExpiresFromColdItIsBumpedToWarm() { + this.Warmup(); + lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(1, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); + lru.GetOrAdd(3, valueFactory.Create); // push 0 to cold (not touched in hot) - // touch 0 while it is in cold - lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(0, valueFactory.Create); // Touch 0 in cold - lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(4, valueFactory.Create); // fully cycle cold, this will evict 0 if it is not moved to warm lru.GetOrAdd(5, valueFactory.Create); lru.GetOrAdd(6, valueFactory.Create); + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(1); - lru.ColdCount.Should().Be(3); + lru.TryGet(0, out var value).Should().BeTrue(); } [Fact] public void WhenValueIsNotTouchedAndExpiresFromColdItIsRemoved() { + this.Warmup(); + lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(1, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); - lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(3, valueFactory.Create); // push 0 to cold (not touched in hot) + + // Don't touch 0 in cold + + lru.GetOrAdd(4, valueFactory.Create); // fully cycle cold, this will evict 0 if it is not moved to warm lru.GetOrAdd(5, valueFactory.Create); lru.GetOrAdd(6, valueFactory.Create); + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); - // insert 7, 0th item will expire from cold - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(0); - lru.ColdCount.Should().Be(3); - - lru.TryGet(0, out var value).Should().Be(false); + lru.TryGet(0, out var value).Should().BeFalse(); } [Fact] public void WhenValueIsNotTouchedAndExpiresFromWarmValueIsBumpedToCold() { - // first 4 values are touched in hot, promote to warm - lru.GetOrAdd(0, valueFactory.Create); + this.Warmup(); + lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(0, valueFactory.Create); // Touch 0 in hot, it will promote to warm + lru.GetOrAdd(1, valueFactory.Create); - lru.GetOrAdd(1, valueFactory.Create); - lru.GetOrAdd(2, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); + lru.GetOrAdd(3, valueFactory.Create); // push 0 to warm - // 3 values added to hot fill warm - lru.GetOrAdd(4, valueFactory.Create); - lru.GetOrAdd(5, valueFactory.Create); - lru.GetOrAdd(6, valueFactory.Create); + // touch next 3 values, so they will promote to warm + lru.GetOrAdd(4, valueFactory.Create); lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(5, valueFactory.Create); lru.GetOrAdd(5, valueFactory.Create); + lru.GetOrAdd(6, valueFactory.Create); lru.GetOrAdd(6, valueFactory.Create); - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(3); - lru.ColdCount.Should().Be(1); + // push 4,5,6 to warm, 0 to cold + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); + + // verify 0 is present, but don't touch it + lru.Keys.Should().Contain(0); + + // push 7,8,9 to cold, evict 0 + lru.GetOrAdd(10, valueFactory.Create); + lru.GetOrAdd(11, valueFactory.Create); + lru.GetOrAdd(12, valueFactory.Create); + + lru.TryGet(0, out var value).Should().BeFalse(); } [Fact] public void WhenValueIsTouchedAndExpiresFromWarmValueIsBumpedBackIntoWarm() { - // first 4 values are touched in hot, promote to warm - lru.GetOrAdd(0, valueFactory.Create); + this.Warmup(); + lru.GetOrAdd(0, valueFactory.Create); + lru.GetOrAdd(0, valueFactory.Create); // Touch 0 in hot, it will promote to warm + lru.GetOrAdd(1, valueFactory.Create); - lru.GetOrAdd(1, valueFactory.Create); - lru.GetOrAdd(2, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); - lru.GetOrAdd(3, valueFactory.Create); + lru.GetOrAdd(3, valueFactory.Create); // push 0 to warm - // touch 0 while it is warm - lru.GetOrAdd(0, valueFactory.Create); + // touch next 3 values, so they will promote to warm + lru.GetOrAdd(4, valueFactory.Create); lru.GetOrAdd(4, valueFactory.Create); + lru.GetOrAdd(5, valueFactory.Create); lru.GetOrAdd(5, valueFactory.Create); + lru.GetOrAdd(6, valueFactory.Create); lru.GetOrAdd(6, valueFactory.Create); - // 3 values added to hot fill warm. Only 0 is touched. - lru.GetOrAdd(4, valueFactory.Create); - lru.GetOrAdd(5, valueFactory.Create); - lru.GetOrAdd(6, valueFactory.Create); + // push 4,5,6 to warm, 0 to cold + lru.GetOrAdd(7, valueFactory.Create); + lru.GetOrAdd(8, valueFactory.Create); + lru.GetOrAdd(9, valueFactory.Create); - // When warm fills, 2 items are processed. 1 is promoted back into warm, and 1 into cold. - lru.HotCount.Should().Be(3); - lru.WarmCount.Should().Be(3); - lru.ColdCount.Should().Be(1); + // Touch 0 + lru.TryGet(0, out var value).Should().BeTrue(); + + // push 7,8,9 to cold, cycle 0 back to warm + lru.GetOrAdd(10, valueFactory.Create); + lru.GetOrAdd(11, valueFactory.Create); + lru.GetOrAdd(12, valueFactory.Create); + + lru.TryGet(0, out value).Should().BeTrue(); } [Fact] @@ -399,13 +463,20 @@ public void WhenValueExpiresItIsDisposed() var lruOfDisposable = new ConcurrentLru(1, new EqualCapacityPartition(6), EqualityComparer.Default); var disposableValueFactory = new DisposableValueFactory(); - for (int i = 0; i < 5; i++) + for (int i = 0; i < 7; i++) { lruOfDisposable.GetOrAdd(i, disposableValueFactory.Create); } - disposableValueFactory.Items[0].IsDisposed.Should().BeTrue(); + disposableValueFactory.Items[0].IsDisposed.Should().BeFalse(); disposableValueFactory.Items[1].IsDisposed.Should().BeFalse(); + + disposableValueFactory.Items[2].IsDisposed.Should().BeTrue(); + + disposableValueFactory.Items[3].IsDisposed.Should().BeFalse(); + disposableValueFactory.Items[4].IsDisposed.Should().BeFalse(); + disposableValueFactory.Items[5].IsDisposed.Should().BeFalse(); + disposableValueFactory.Items[6].IsDisposed.Should().BeFalse(); } [Fact] @@ -414,19 +485,23 @@ public void WhenValueEvictedItemRemovedEventIsFired() var lruEvents = new ConcurrentLru(1, new EqualCapacityPartition(6), EqualityComparer.Default); lruEvents.ItemRemoved += OnLruItemRemoved; - for (int i = 0; i < 6; i++) + // First 6 adds + // hot[6, 5], warm[2, 1], cold[4, 3] + // => + // hot[8, 7], warm[1, 0], cold[6, 5], evicted[4, 3] + for (int i = 0; i < 8; i++) { lruEvents.GetOrAdd(i + 1, i => i + 1); } removedItems.Count.Should().Be(2); - removedItems[0].Key.Should().Be(1); - removedItems[0].Value.Should().Be(2); + removedItems[0].Key.Should().Be(3); + removedItems[0].Value.Should().Be(4); removedItems[0].Reason.Should().Be(ItemRemovedReason.Evicted); - removedItems[1].Key.Should().Be(2); - removedItems[1].Value.Should().Be(3); + removedItems[1].Key.Should().Be(4); + removedItems[1].Value.Should().Be(5); removedItems[1].Reason.Should().Be(ItemRemovedReason.Evicted); } @@ -578,7 +653,7 @@ public void WhenKeyDoesNotExistAddOrUpdateMaintainsLruOrder() lru.AddOrUpdate(4, "4"); lru.HotCount.Should().Be(3); - lru.ColdCount.Should().Be(1); // items must have been enqueued and cycled for one of them to reach the cold queue + lru.WarmCount.Should().Be(1); // items must have been enqueued and cycled for one of them to reach the warm queue } [Fact] @@ -654,6 +729,7 @@ public void WhenTrimCountIsMoreThanCapacityThrows() lru.Invoking(l => lru.Trim(hotCap + warmCap + coldCap + 1)).Should().Throw(); } + // TODO: review these tests - warmup changes sequence [Theory] [InlineData(1, new[] { 9, 8, 7, 3, 2, 1, 6, 5 })] [InlineData(2, new[] { 9, 8, 7, 3, 2, 1, 6 })] @@ -834,5 +910,18 @@ public void WhenItemsAreTrimmedAnEventIsFired() removedItems[1].Value.Should().Be(3); removedItems[1].Reason.Should().Be(ItemRemovedReason.Trimmed); } + + private void Warmup() + { + lru.GetOrAdd(-1, valueFactory.Create); + lru.GetOrAdd(-2, valueFactory.Create); + lru.GetOrAdd(-3, valueFactory.Create); + lru.GetOrAdd(-4, valueFactory.Create); + lru.GetOrAdd(-5, valueFactory.Create); + lru.GetOrAdd(-6, valueFactory.Create); + lru.GetOrAdd(-7, valueFactory.Create); + lru.GetOrAdd(-8, valueFactory.Create); + lru.GetOrAdd(-9, valueFactory.Create); + } } } diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs index ba58e0c0..6bd6588f 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs @@ -88,19 +88,23 @@ public void WhenValueEvictedItemRemovedEventIsFired() var lruEvents = new ConcurrentTLru(1, new EqualCapacityPartition(6), EqualityComparer.Default, timeToLive); lruEvents.ItemRemoved += OnLruItemRemoved; - for (int i = 0; i < 6; i++) + // First 6 adds + // hot[6, 5], warm[2, 1], cold[4, 3] + // => + // hot[8, 7], warm[1, 0], cold[6, 5], evicted[4, 3] + for (int i = 0; i < 8; i++) { lruEvents.GetOrAdd(i + 1, i => i + 1); } removedItems.Count.Should().Be(2); - removedItems[0].Key.Should().Be(1); - removedItems[0].Value.Should().Be(2); + removedItems[0].Key.Should().Be(3); + removedItems[0].Value.Should().Be(4); removedItems[0].Reason.Should().Be(ItemRemovedReason.Evicted); - removedItems[1].Key.Should().Be(2); - removedItems[1].Value.Should().Be(3); + removedItems[1].Key.Should().Be(4); + removedItems[1].Value.Should().Be(5); removedItems[1].Reason.Should().Be(ItemRemovedReason.Evicted); } From 1ff5ce50149c16df4d8b9ac07581836d7e7f5ddb Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Jul 2022 16:22:52 -0700 Subject: [PATCH 3/5] rem hit rate changes --- .../Arc/Analysis.cs | 13 +++---- .../Arc/ArcDataFile.cs | 38 +++---------------- .../Arc/Runner.cs | 15 ++------ 3 files changed, 16 insertions(+), 50 deletions(-) diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs b/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs index cac6079a..4727fbd6 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs @@ -13,14 +13,13 @@ namespace BitFaster.Caching.HitRateAnalysis.Arc { public class Analysis { - private readonly ConcurrentLru concurrentLru; - private readonly ClassicLru classicLru; - private static readonly object dummy = new object(); + private readonly ConcurrentLru concurrentLru; + private readonly ClassicLru classicLru; public Analysis(int cacheSize) { - concurrentLru = new ConcurrentLru(1, cacheSize, EqualityComparer.Default); - classicLru = new ClassicLru(1, cacheSize, EqualityComparer.Default); + concurrentLru = new ConcurrentLru(1, cacheSize, EqualityComparer.Default); + classicLru = new ClassicLru(1, cacheSize, EqualityComparer.Default); } public int CacheSize => concurrentLru.Capacity; @@ -31,8 +30,8 @@ public Analysis(int cacheSize) public void TestKey(long key) { - concurrentLru.GetOrAdd(key, u => dummy); - classicLru.GetOrAdd(key, u => dummy); + concurrentLru.GetOrAdd(key, u => 1); + classicLru.GetOrAdd(key, u => 1); } public static void WriteToFile(string path, IEnumerable results) diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs b/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs index 7066bcab..4afaae48 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/ArcDataFile.cs @@ -68,7 +68,7 @@ public async Task DownloadIfNotExistsAsync() } } - public IEnumerable EnumerateFileData() + public IEnumerable EnumerateFileData() { // File Format: // Every line in every file has four fields. @@ -98,45 +98,19 @@ public IEnumerable EnumerateFileData() while (sr.Peek() >= 0) { var line = sr.ReadLine(); + var chunks = line.Split(' '); - ReadOnlySpan buffer = line.AsSpan(); - - var chunks = buffer.Split(' '); - - if (chunks.MoveNext()) + if (long.TryParse(chunks[0], out var startBlock)) { - if (long.TryParse(buffer[chunks.Current], out long startBlock)) + if (int.TryParse(chunks[1], out var sequence)) { - if (chunks.MoveNext()) + for (long i = startBlock; i < startBlock + sequence; i++) { - if (int.TryParse(buffer[chunks.Current], out var sequence)) - { - yield return new BlockRange(startBlock, sequence); - - //for (long i = startBlock; i < startBlock + sequence; i++) - //{ - // yield return i; - //} - } + yield return i; } } } } } } - - public class BlockRange - { - private readonly long start; - private int sequence; - - public BlockRange(long start, int sequence) - { - this.start = start; - this.sequence = sequence; - } - - public long Start => this.start; - public int Sequence => this.sequence; - } } diff --git a/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs b/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs index 6acb729a..b44f8b75 100644 --- a/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs +++ b/BitFaster.Caching.HitRateAnalysis/Arc/Runner.cs @@ -23,29 +23,22 @@ public async Task Run() Console.WriteLine("Running..."); int count = 0; - int keys = 0; var sw = Stopwatch.StartNew(); - foreach (var block in this.config.File.EnumerateFileData()) + foreach (var key in this.config.File.EnumerateFileData()) { foreach (var a in this.config.Analysis) { - for (long i = block.Start; i < block.Start + block.Sequence; i++) - { - a.TestKey(i); - } - - + a.TestKey(key); } - keys += block.Sequence; if (++count % 100000 == 0) { - Console.WriteLine($"Processed {keys} keys..."); + Console.WriteLine($"Processed {count} keys..."); } } - Console.WriteLine($"Tested {keys} keys in {sw.Elapsed}"); + Console.WriteLine($"Tested {count} keys in {sw.Elapsed}"); this.config.Analysis.WriteToConsole(); Analysis.WriteToFile(this.config.Name, this.config.Analysis); From 86ebfe1efa8c523e6c46d42f362ae0f80d34fe9c Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Jul 2022 16:29:57 -0700 Subject: [PATCH 4/5] increase test wait --- BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs index 6bd6588f..258de271 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs @@ -169,7 +169,7 @@ public async Task WhenCacheHasExpiredAndFreshItemsExpireRemovesOnlyExpiredItems( lru.AddOrUpdate(5, "5"); lru.AddOrUpdate(6, "6"); - await Task.Delay(timeToLive * 2); + await Task.Delay(timeToLive * 4); lru.GetOrAdd(1, valueFactory.Create); lru.GetOrAdd(2, valueFactory.Create); From 0f42244d82b7fdf05f8955faae2d3850d7e339f3 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 16 Jul 2022 16:38:22 -0700 Subject: [PATCH 5/5] cleanup --- .../Lru/ConcurrentLruTests.cs | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs index 24dfc07a..acdaa0e3 100644 --- a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs +++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs @@ -282,28 +282,6 @@ public void WhenKeysAreContinuouslyRequestedInTheOrderTheyAreAddedCountIsBounded } } - // [Theory] - // [InlineData(new[] { 9 }, new[] { 9, 8, 7, 3, 2, 1, 6, 5 })] - //[InlineData(2, new[] { 9, 8, 7, 3, 2, 1, 6 })] - //[InlineData(3, new[] { 9, 8, 7, 3, 2, 1 })] - //[InlineData(4, new[] { 9, 8, 7, 3, 2 })] - //[InlineData(5, new[] { 9, 8, 7, 3 })] - //[InlineData(6, new[] { 9, 8, 7 })] - //[InlineData(7, new[] { 9, 8 })] - //[InlineData(8, new[] { 9 })] - //[InlineData(9, new int[] { })] - //public void EvictionFlow(int[] input, int[] expected) - //{ - // this.Warmup(); - - // foreach (var k in input) - // { - // this.lru.GetOrAdd(k, valueFactory.Create); - // } - - // lru.Keys.Should().BeEquivalentTo(expected); - //} - [Fact] public void WhenValueIsNotTouchedAndExpiresFromHotValueIsBumpedToCold() { @@ -729,7 +707,6 @@ public void WhenTrimCountIsMoreThanCapacityThrows() lru.Invoking(l => lru.Trim(hotCap + warmCap + coldCap + 1)).Should().Throw(); } - // TODO: review these tests - warmup changes sequence [Theory] [InlineData(1, new[] { 9, 8, 7, 3, 2, 1, 6, 5 })] [InlineData(2, new[] { 9, 8, 7, 3, 2, 1, 6 })]