From e40703f36fb1601c4c31b29c1e4485ef31773513 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 27 Aug 2022 16:17:36 -0700 Subject: [PATCH 1/8] nostripew --- BitFaster.Caching/Lfu/ConcurrentLfu.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index de0704ab..45c69b83 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -33,7 +33,7 @@ namespace BitFaster.Caching.Lfu /// public class ConcurrentLfu : ICache, IAsyncCache, IBoundedPolicy { - private const int MaxWriteBufferRetries = 100; + private const int MaxWriteBufferRetries = 16; private const int TakeBufferSize = 1024; public const int BufferSize = 128; @@ -41,7 +41,7 @@ public class ConcurrentLfu : ICache, IAsyncCache, IBoundedPoli private readonly ConcurrentDictionary> dictionary; private readonly StripedMpscBuffer> readBuffer; - private readonly StripedMpscBuffer> writeBuffer; + private readonly MpscBoundedBuffer> writeBuffer; private readonly CacheMetrics metrics = new CacheMetrics(); @@ -76,7 +76,8 @@ public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler) this.readBuffer = new StripedMpscBuffer>(concurrencyLevel, BufferSize); // TODO: how big should this be in total? We shouldn't allow more than some capacity % of writes in the buffer - this.writeBuffer = new StripedMpscBuffer>(concurrencyLevel, BufferSize); + int writeBuffer = Math.Min(capacity / 10, BufferSize); + this.writeBuffer = new MpscBoundedBuffer>(writeBuffer); this.cmSketch = new CmSketch(1, comparer); this.cmSketch.EnsureCapacity(capacity); @@ -407,7 +408,7 @@ private bool Maintenance() wasDrained = count == 0; } - count = this.writeBuffer.DrainTo(localDrainBuffer); + count = this.writeBuffer.DrainTo(new ArraySegment>(localDrainBuffer)); for (int i = 0; i < count; i++) { From 28d4de03239d733fa201a69ee4ecfc0db688f373 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sat, 27 Aug 2022 16:20:46 -0700 Subject: [PATCH 2/8] comment --- BitFaster.Caching/Lfu/ConcurrentLfu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index 45c69b83..c5d54c98 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -75,9 +75,9 @@ public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler) this.readBuffer = new StripedMpscBuffer>(concurrencyLevel, BufferSize); - // TODO: how big should this be in total? We shouldn't allow more than some capacity % of writes in the buffer - int writeBuffer = Math.Min(capacity / 10, BufferSize); - this.writeBuffer = new MpscBoundedBuffer>(writeBuffer); + // Cap the write buffer to 10% of the cache size, or BufferSize. Whichever is smaller. + int writeBufferSize = Math.Min(capacity / 10, BufferSize); + this.writeBuffer = new MpscBoundedBuffer>(writeBufferSize); this.cmSketch = new CmSketch(1, comparer); this.cmSketch.EnsureCapacity(capacity); From 93f394643cecb3d9a7f0246d6b45ef308fe08119 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Sun, 28 Aug 2022 21:56:37 -0700 Subject: [PATCH 3/8] fix test --- .../Lfu/ConcurrentLfuTests.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index b54457d4..930a8c02 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -89,13 +89,7 @@ public void WhenItemIsEvictedItIsDisposed() LogLru(); dcache.Count.Should().Be(20); - - for (int i = 0; i < 5; i++) - { - disposables[i].IsDisposed.Should().BeTrue(); - } - - disposables[5].IsDisposed.Should().BeFalse(); + disposables.Count(d => d.IsDisposed).Should().Be(5); } // protected 15 @@ -421,18 +415,18 @@ public void WhenWriteBufferIsFullAddDoesMaintenance() // add an item, flush write buffer cache.GetOrAdd(-1, k => k); - scheduler.RunCount.Should().Be(1); + //scheduler.RunCount.Should().Be(1); cache.PendingMaintenance(); // remove the item but don't flush, it is now in the write buffer and maintenance is scheduled cache.TryRemove(-1).Should().BeTrue(); - scheduler.RunCount.Should().Be(2); + //scheduler.RunCount.Should().Be(2); // add buffer size items, last iteration will invoke maintenance on the foreground since write // buffer is full and test scheduler did not do any work for (int i = 0; i < ConcurrentLfu.BufferSize; i++) { - scheduler.RunCount.Should().Be(2); + //scheduler.RunCount.Should().Be(2); cache.GetOrAdd(i, k => k); } @@ -459,7 +453,8 @@ public void WhenWriteBufferIsFullUpdatesAreDropped() cache.PendingMaintenance(); - cache.Metrics.Value.Updated.Should().Be(bufferSize); + // buffer size is 2 (10% of 20) + cache.Metrics.Value.Updated.Should().Be(2); } [Fact] From 12cae9dbc6932b836aa2c015f1725ef92d08a44f Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Thu, 8 Sep 2022 17:35:05 -0700 Subject: [PATCH 4/8] size --- BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs | 9 ++++----- BitFaster.Caching/Lfu/ConcurrentLfu.cs | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index 2f12b586..785e97a0 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -439,10 +439,11 @@ public void WhenWriteBufferIsFullAddDoesMaintenance() [Fact] public void WhenWriteBufferIsFullUpdatesAreDropped() { - var bufferSize = LfuBufferSize.DefaultBufferSize; + int capacity = 20; + var bufferSize = Math.Min(BitOps.CeilingPowerOfTwo(capacity), 128); var scheduler = new TestScheduler(); var bufferConfig = new LfuBufferSize(new StripedBufferSize(bufferSize, 1), new StripedBufferSize(bufferSize, 1)); - cache = new ConcurrentLfu(1, 20, scheduler, EqualityComparer.Default, bufferConfig); + cache = new ConcurrentLfu(1, capacity, scheduler, EqualityComparer.Default, bufferConfig); cache.GetOrAdd(1, k => k); scheduler.RunCount.Should().Be(1); @@ -455,8 +456,7 @@ public void WhenWriteBufferIsFullUpdatesAreDropped() cache.PendingMaintenance(); - // buffer size is 2 (10% of 20) - cache.Metrics.Value.Updated.Should().Be(2); + cache.Metrics.Value.Updated.Should().Be(bufferSize); } [Fact] @@ -797,7 +797,6 @@ await Threaded.Run(threads, i => this.output.WriteLine($"Maintenance ops {this.cache.Scheduler.RunCount}"); cache.Metrics.Value.Misses.Should().Be(iterations * threads); - cache.Count.Should().BeCloseTo(20, 1); } private void VerifyHits(int iterations, int minSamples) diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index b64e0339..9db31742 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -83,8 +83,8 @@ public ConcurrentLfu(int concurrencyLevel, int capacity, IScheduler scheduler, I this.readBuffer = new StripedMpscBuffer>(bufferSize.Read); - // Cap the write buffer to 10% of the cache size, or BufferSize. Whichever is smaller. - int writeBufferSize = Math.Min(capacity / 10, 128); + // Cap the write buffer to the cache size, or 128. Whichever is smaller. + int writeBufferSize = Math.Min(BitOps.CeilingPowerOfTwo(capacity), 128); this.writeBuffer = new MpscBoundedBuffer>(writeBufferSize); this.cmSketch = new CmSketch(1, comparer); @@ -450,7 +450,7 @@ private bool Maintenance(LfuNode droppedWrite = null) OnAccess(localDrainBuffer[i]); } - count = this.writeBuffer.DrainTo(new ArraySegment>(localDrainBuffer)); + int writeCount = this.writeBuffer.DrainTo(new ArraySegment>(localDrainBuffer)); for (int i = 0; i < writeCount; i++) { @@ -822,7 +822,7 @@ public LfuDebugView(ConcurrentLfu lfu) public StripedMpscBuffer> ReadBuffer => this.lfu.readBuffer; - public StripedMpscBuffer> WriteBuffer => this.lfu.writeBuffer; + public MpscBoundedBuffer> WriteBuffer => this.lfu.writeBuffer; public KeyValuePair[] Items { From 9d502823d23630dd57683d3f8337fcd04259a5ef Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Thu, 8 Sep 2022 18:00:16 -0700 Subject: [PATCH 5/8] tests --- BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index 785e97a0..ea1bc245 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -416,18 +416,15 @@ public void WhenWriteBufferIsFullAddDoesMaintenance() // add an item, flush write buffer cache.GetOrAdd(-1, k => k); - //scheduler.RunCount.Should().Be(1); cache.PendingMaintenance(); // remove the item but don't flush, it is now in the write buffer and maintenance is scheduled cache.TryRemove(-1).Should().BeTrue(); - //scheduler.RunCount.Should().Be(2); // add buffer size items, last iteration will invoke maintenance on the foreground since write // buffer is full and test scheduler did not do any work for (int i = 0; i < bufferSize; i++) { - //scheduler.RunCount.Should().Be(2); cache.GetOrAdd(i, k => k); } From a008118845a86ac1fc86b9db546639092ca2916b Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Thu, 8 Sep 2022 18:37:55 -0700 Subject: [PATCH 6/8] rem rbuff --- .../Lfu/ConcurrentLfuTests.cs | 8 +++---- BitFaster.Caching/Lfu/LfuBufferSize.cs | 22 +++---------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs index ea1bc245..f2822db7 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuTests.cs @@ -411,7 +411,7 @@ public void WhenWriteBufferIsFullAddDoesMaintenance() var bufferSize = LfuBufferSize.DefaultBufferSize; var scheduler = new TestScheduler(); - var bufferConfig = new LfuBufferSize(new StripedBufferSize(bufferSize, 1), new StripedBufferSize(bufferSize, 1)); + var bufferConfig = new LfuBufferSize(new StripedBufferSize(bufferSize, 1)); cache = new ConcurrentLfu(1, bufferSize * 2, scheduler, EqualityComparer.Default, bufferConfig); // add an item, flush write buffer @@ -439,7 +439,7 @@ public void WhenWriteBufferIsFullUpdatesAreDropped() int capacity = 20; var bufferSize = Math.Min(BitOps.CeilingPowerOfTwo(capacity), 128); var scheduler = new TestScheduler(); - var bufferConfig = new LfuBufferSize(new StripedBufferSize(bufferSize, 1), new StripedBufferSize(bufferSize, 1)); + var bufferConfig = new LfuBufferSize(new StripedBufferSize(bufferSize, 1)); cache = new ConcurrentLfu(1, capacity, scheduler, EqualityComparer.Default, bufferConfig); cache.GetOrAdd(1, k => k); @@ -736,7 +736,7 @@ public void VerifyHitsWithForegroundScheduler() public void VerifyMisses() { cache = new ConcurrentLfu(1, 20, new BackgroundThreadScheduler(), EqualityComparer.Default, - new LfuBufferSize(new StripedBufferSize(1, 1), new StripedBufferSize(1, 1))); + new LfuBufferSize(new StripedBufferSize(1, 1))); int iterations = 100000; Func func = x => x; @@ -771,7 +771,7 @@ public async Task ThreadedVerifyMisses() { // buffer size is 1, this will cause dropped writes on some threads where the buffer is full cache = new ConcurrentLfu(1, 20, new NullScheduler(), EqualityComparer.Default, - new LfuBufferSize(new StripedBufferSize(1, 1), new StripedBufferSize(1, 1))); + new LfuBufferSize(new StripedBufferSize(1, 1))); int threads = 4; int iterations = 100000; diff --git a/BitFaster.Caching/Lfu/LfuBufferSize.cs b/BitFaster.Caching/Lfu/LfuBufferSize.cs index 4f7f582c..862224a0 100644 --- a/BitFaster.Caching/Lfu/LfuBufferSize.cs +++ b/BitFaster.Caching/Lfu/LfuBufferSize.cs @@ -14,17 +14,13 @@ public class LfuBufferSize /// public const int DefaultBufferSize = 128; - private const int MaxWriteBufferTotalSize = 1024; - /// /// Initializes a new instance of the LfuBufferSize class with the specified read and write buffer sizes. /// /// The read buffer size. - /// The write buffer size. - public LfuBufferSize(StripedBufferSize readBufferSize, StripedBufferSize writeBufferSize) + public LfuBufferSize(StripedBufferSize readBufferSize) { Read = readBufferSize ?? throw new ArgumentNullException(nameof(readBufferSize)); - Write = writeBufferSize ?? throw new ArgumentNullException(nameof(writeBufferSize)); } /// @@ -32,11 +28,6 @@ public LfuBufferSize(StripedBufferSize readBufferSize, StripedBufferSize writeBu /// public StripedBufferSize Read { get; } - /// - /// Gets the write buffer size. - /// - public StripedBufferSize Write { get; } - /// /// Estimates default buffer sizes intended to give optimal throughput. /// @@ -48,8 +39,7 @@ public static LfuBufferSize Default(int concurrencyLevel, int capacity) if (capacity < 13) { return new LfuBufferSize( - new StripedBufferSize(32, 1), - new StripedBufferSize(16, 1)); + new StripedBufferSize(32, 1)); } // cap concurrency at proc count * 2 @@ -61,14 +51,8 @@ public static LfuBufferSize Default(int concurrencyLevel, int capacity) concurrencyLevel /= 2; } - // Constrain write buffer size so that the LFU dictionary will not ever end up with more than 2x cache - // capacity entries before maintenance runs. - int writeBufferTotalSize = Math.Min(BitOps.CeilingPowerOfTwo(capacity), MaxWriteBufferTotalSize); - int writeStripeSize = Math.Min(BitOps.CeilingPowerOfTwo(Math.Max(writeBufferTotalSize / concurrencyLevel, 4)), 128); - return new LfuBufferSize( - new StripedBufferSize(DefaultBufferSize, concurrencyLevel), - new StripedBufferSize(writeStripeSize, concurrencyLevel)); + new StripedBufferSize(DefaultBufferSize, concurrencyLevel)); } } } From 8a61bcb69de05bf70a6759272e6b7788ad04b167 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Thu, 8 Sep 2022 18:45:49 -0700 Subject: [PATCH 7/8] test --- BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuBuilderTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuBuilderTests.cs b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuBuilderTests.cs index 06b41c4b..ea5a8973 100644 --- a/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuBuilderTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/ConcurrentLfuBuilderTests.cs @@ -58,7 +58,6 @@ public void TestBufferConfiguraiton() { ICache lfu = new ConcurrentLfuBuilder() .WithBufferConfiguration(new LfuBufferSize( - new StripedBufferSize(128, 2), new StripedBufferSize(128, 2) )) .Build(); From 9cd7243ea0354983dbc03bcb35ba15840fb2874f Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Thu, 8 Sep 2022 18:48:43 -0700 Subject: [PATCH 8/8] test --- .../Lfu/LfuBufferSizeTests.cs | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/BitFaster.Caching.UnitTests/Lfu/LfuBufferSizeTests.cs b/BitFaster.Caching.UnitTests/Lfu/LfuBufferSizeTests.cs index 8cbec085..ec3936b6 100644 --- a/BitFaster.Caching.UnitTests/Lfu/LfuBufferSizeTests.cs +++ b/BitFaster.Caching.UnitTests/Lfu/LfuBufferSizeTests.cs @@ -11,32 +11,24 @@ public class LfuBufferSizeTests [Fact] public void WhenReadBufferIsNullThrows() { - Action constructor = () => { var x = new LfuBufferSize(null, new StripedBufferSize(1, 1)); }; - - constructor.Should().Throw(); - } - - [Fact] - public void WhenWriteBufferIsNullThrows() - { - Action constructor = () => { var x = new LfuBufferSize(new StripedBufferSize(1, 1), null); }; + Action constructor = () => { var x = new LfuBufferSize(null); }; constructor.Should().Throw(); } [SkippableTheory] - [InlineData(1, 3, 1, 32, 1, 16)] - [InlineData(1, 14, 1, 128, 1, 16)] - [InlineData(1, 50, 1, 128, 1, 64)] - [InlineData(1, 100, 1, 128, 1, 128)] - [InlineData(4, 100, 4, 128, 4, 32)] - [InlineData(16, 100, 8, 128, 8, 16)] // fails win - [InlineData(64, 100, 8, 128, 8, 16)] // fails win - [InlineData(1, 1000, 1, 128, 1, 128)] - [InlineData(4, 1000, 4, 128, 4, 128)] - [InlineData(32, 1000, 32, 128, 32, 32)] // fails win + fails mac - [InlineData(256, 100000, 32, 128, 32, 32)] // fails win + fails mac - public void CalculateDefaultBufferSize(int concurrencyLevel, int capacity, int expectedReadStripes, int expectedReadBuffer, int expecteWriteStripes, int expecteWriteBuffer) + [InlineData(1, 3, 1, 32)] + [InlineData(1, 14, 1, 128)] + [InlineData(1, 50, 1, 128)] + [InlineData(1, 100, 1, 128)] + [InlineData(4, 100, 4, 128)] + [InlineData(16, 100, 8, 128)] // fails win + [InlineData(64, 100, 8, 128)] // fails win + [InlineData(1, 1000, 1, 128)] + [InlineData(4, 1000, 4, 128)] + [InlineData(32, 1000, 32, 128)] // fails win + fails mac + [InlineData(256, 100000, 32, 128)] // fails win + fails mac + public void CalculateDefaultBufferSize(int concurrencyLevel, int capacity, int expectedReadStripes, int expectedReadBuffer) { // Some of these tests depend on the CPU Core count - skip if run on a different config machine. bool notExpectedCpuCount = Environment.ProcessorCount != 12; @@ -48,8 +40,6 @@ public void CalculateDefaultBufferSize(int concurrencyLevel, int capacity, int e bufferSize.Read.StripeCount.Should().Be(expectedReadStripes); bufferSize.Read.BufferSize.Should().Be(expectedReadBuffer); - bufferSize.Write.StripeCount.Should().Be(expecteWriteStripes); - bufferSize.Write.BufferSize.Should().Be(expecteWriteBuffer); } } }