Skip to content

Commit e4d397a

Browse files
authored
Merge branch 'main' into users/alexpeck/arm64
2 parents 9475c56 + 07f8a1c commit e4d397a

File tree

13 files changed

+504
-43
lines changed

13 files changed

+504
-43
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using Benchly;
2+
using BenchmarkDotNet.Attributes;
3+
using BenchmarkDotNet.Diagnosers;
4+
using BenchmarkDotNet.Jobs;
5+
using BitFaster.Caching.Lfu;
6+
using BitFaster.Caching.Lru;
7+
using BitFaster.Caching.Scheduler;
8+
using Microsoft.Extensions.Caching.Memory;
9+
using System;
10+
using System.Collections.Concurrent;
11+
using System.Collections.Generic;
12+
using System.Threading;
13+
14+
namespace BitFaster.Caching.Benchmarks
15+
{
16+
17+
#if Windows
18+
[DisassemblyDiagnoser(printSource: true, maxDepth: 5)]
19+
[SimpleJob(RuntimeMoniker.Net48)]
20+
#endif
21+
[SimpleJob(RuntimeMoniker.Net60)]
22+
[MemoryDiagnoser(displayGenColumns: false)]
23+
// [HardwareCounters(HardwareCounter.LlcMisses, HardwareCounter.CacheMisses)] // Requires Admin https://adamsitnik.com/Hardware-Counters-Diagnoser/
24+
// [ThreadingDiagnoser] // Requires .NET Core
25+
[HideColumns("Job", "Median", "RatioSD", "Alloc Ratio")]
26+
[ColumnChart(Title= "Guid Lookup Latency ({JOB})", Output = OutputMode.PerJob, Colors = "darkslategray,royalblue,royalblue,#ffbf00,indianred,indianred")]
27+
public class LruJustGetOrAddGuid
28+
{
29+
private static readonly ConcurrentDictionary<int, Guid> dictionary = new ConcurrentDictionary<int, Guid>(8, 9, EqualityComparer<int>.Default);
30+
31+
private static readonly ConcurrentLru<int, Guid> concurrentLru = new ConcurrentLru<int, Guid>(8, 9, EqualityComparer<int>.Default);
32+
private static readonly FastConcurrentLru<int, Guid> fastConcurrentLru = new FastConcurrentLru<int, Guid>(8, 9, EqualityComparer<int>.Default);
33+
34+
35+
private static readonly BackgroundThreadScheduler background = new BackgroundThreadScheduler();
36+
private static readonly ConcurrentLfu<int, Guid> concurrentLfu = new ConcurrentLfu<int, Guid>(1, 9, background, EqualityComparer<int>.Default);
37+
38+
private static readonly int key = 1;
39+
private static System.Runtime.Caching.MemoryCache memoryCache = System.Runtime.Caching.MemoryCache.Default;
40+
41+
Microsoft.Extensions.Caching.Memory.MemoryCache exMemoryCache
42+
= new Microsoft.Extensions.Caching.Memory.MemoryCache(new MemoryCacheOptionsAccessor());
43+
44+
private static readonly byte[] b = new byte[8];
45+
46+
[GlobalSetup]
47+
public void GlobalSetup()
48+
{
49+
memoryCache.Set(key.ToString(), new Guid(key, 0, 0, b), new System.Runtime.Caching.CacheItemPolicy());
50+
exMemoryCache.Set(key, new Guid(key, 0, 0, b));
51+
}
52+
53+
[GlobalCleanup]
54+
public void GlobalCleanup()
55+
{
56+
background.Dispose();
57+
}
58+
59+
[Benchmark(Baseline = true)]
60+
public Guid ConcurrentDictionary()
61+
{
62+
Func<int, Guid> func = x => new Guid(x, 0, 0, b);
63+
return dictionary.GetOrAdd(1, func);
64+
}
65+
66+
[Benchmark()]
67+
public Guid FastConcurrentLru()
68+
{
69+
Func<int, Guid> func = x => new Guid(x, 0, 0, b);
70+
return fastConcurrentLru.GetOrAdd(1, func);
71+
}
72+
73+
[Benchmark()]
74+
public Guid ConcurrentLru()
75+
{
76+
Func<int, Guid> func = x => new Guid(x, 0, 0, b);
77+
return concurrentLru.GetOrAdd(1, func);
78+
}
79+
80+
[Benchmark()]
81+
public Guid ConcurrentLfu()
82+
{
83+
Func<int, Guid> func = x => new Guid(x, 0, 0, b);
84+
return concurrentLfu.GetOrAdd(1, func);
85+
}
86+
87+
[Benchmark()]
88+
public Guid RuntimeMemoryCacheGet()
89+
{
90+
return (Guid)memoryCache.Get("1");
91+
}
92+
93+
[Benchmark()]
94+
public Guid ExtensionsMemoryCacheGet()
95+
{
96+
return (Guid)exMemoryCache.Get(1);
97+
}
98+
99+
public class MemoryCacheOptionsAccessor
100+
: Microsoft.Extensions.Options.IOptions<MemoryCacheOptions>
101+
{
102+
private readonly MemoryCacheOptions options = new MemoryCacheOptions();
103+
104+
public MemoryCacheOptions Value => this.options;
105+
106+
}
107+
}
108+
}

BitFaster.Caching.HitRateAnalysis/BitFaster.Caching.HitRateAnalysis.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
<ItemGroup>
2121
<PackageReference Include="ConsoleTables" Version="2.6.1" />
22-
<PackageReference Include="CsvHelper" Version="31.0.3" />
22+
<PackageReference Include="CsvHelper" Version="32.0.3" />
2323
<PackageReference Include="EasyConsole" Version="1.1.0">
2424
<NoWarn>NU1701</NoWarn>
2525
</PackageReference>

BitFaster.Caching.ThroughputAnalysis/BitFaster.Caching.ThroughputAnalysis.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<ItemGroup>
1919
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
2020
<PackageReference Include="ConsoleTables" Version="2.6.1" />
21-
<PackageReference Include="CsvHelper" Version="31.0.3" />
21+
<PackageReference Include="CsvHelper" Version="32.0.3" />
2222
<PackageReference Include="EasyConsole" Version="1.1.0">
2323
<NoWarn>NU1701</NoWarn>
2424
</PackageReference>

BitFaster.Caching.UnitTests.Std/BitFaster.Caching.UnitTests.Std.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1212
</PackageReference>
1313
<PackageReference Include="FluentAssertions" Version="6.12.0" />
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
1515
<PackageReference Include="Moq" Version="4.20.70" />
1616
<PackageReference Include="ObjectLayoutInspector" Version="0.1.4" />
17-
<PackageReference Include="xunit" Version="2.8.0" />
17+
<PackageReference Include="xunit" Version="2.8.1" />
1818
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
1919
<PrivateAssets>all</PrivateAssets>
2020
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

BitFaster.Caching.UnitTests/BitFaster.Caching.UnitTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1212
</PackageReference>
1313
<PackageReference Include="FluentAssertions" Version="6.12.0" />
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
14+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
1515
<PackageReference Include="Moq" Version="4.20.69" />
1616
<PackageReference Include="ObjectLayoutInspector" Version="0.1.4" />
17-
<PackageReference Include="xunit" Version="2.8.0" />
17+
<PackageReference Include="xunit" Version="2.8.1" />
1818
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
1919
<PrivateAssets>all</PrivateAssets>
2020
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

BitFaster.Caching.UnitTests/Lru/ConcurrentLruSoakTests.cs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
4+
using System.Threading;
35
using System.Threading.Tasks;
46
using BitFaster.Caching.Lru;
57
using FluentAssertions;
68
using Xunit;
79
using Xunit.Abstractions;
10+
using static BitFaster.Caching.UnitTests.Lru.LruItemSoakTests;
811

912
namespace BitFaster.Caching.UnitTests.Lru
1013
{
@@ -190,6 +193,29 @@ await Threaded.Run(4, () => {
190193
}
191194
}
192195

196+
[Fact]
197+
public async Task WhenSoakConcurrentGetAndUpdateValueTypeCacheEndsInConsistentState()
198+
{
199+
var lruVT = new ConcurrentLru<int, Guid>(1, capacity, EqualityComparer<int>.Default);
200+
201+
for (int i = 0; i < 10; i++)
202+
{
203+
await Threaded.Run(4, () => {
204+
var b = new byte[8];
205+
for (int i = 0; i < 100000; i++)
206+
{
207+
lruVT.TryUpdate(i + 1, new Guid(i, 0, 0, b));
208+
lruVT.GetOrAdd(i + 1, x => new Guid(x, 0, 0, b));
209+
}
210+
});
211+
212+
this.testOutputHelper.WriteLine($"{lruVT.HotCount} {lruVT.WarmCount} {lruVT.ColdCount}");
213+
this.testOutputHelper.WriteLine(string.Join(" ", lruVT.Keys));
214+
215+
new ConcurrentLruIntegrityChecker<int, Guid, LruItem<int, Guid>, LruPolicy<int, Guid>, TelemetryPolicy<int, Guid>>(lruVT).Validate();
216+
}
217+
}
218+
193219
[Fact]
194220
public async Task WhenAddingCacheSizeItemsNothingIsEvicted()
195221
{
@@ -258,6 +284,52 @@ await Threaded.Run(4, r => {
258284
RunIntegrityCheck();
259285
}
260286

287+
// This test will run forever if there is a live lock.
288+
// Since the cache bookkeeping has some overhead, it is harder to provoke
289+
// spinning inside the reader thread compared to LruItemSoakTests.DetectTornStruct.
290+
[Theory]
291+
[Repeat(10)]
292+
public async Task WhenValueIsBigStructNoLiveLock(int _)
293+
{
294+
using var source = new CancellationTokenSource();
295+
var started = new TaskCompletionSource<bool>();
296+
var cache = new ConcurrentLru<int, Guid>(1, capacity, EqualityComparer<int>.Default);
297+
298+
var setTask = Task.Run(() => Setter(cache, source.Token, started));
299+
await started.Task;
300+
Checker(cache, source);
301+
302+
await setTask;
303+
}
304+
305+
private void Setter(ICache<int, Guid> cache, CancellationToken cancelToken, TaskCompletionSource<bool> started)
306+
{
307+
started.SetResult(true);
308+
309+
while (true)
310+
{
311+
cache.AddOrUpdate(1, Guid.NewGuid());
312+
cache.AddOrUpdate(1, Guid.NewGuid());
313+
314+
if (cancelToken.IsCancellationRequested)
315+
{
316+
return;
317+
}
318+
}
319+
}
320+
321+
private void Checker(ICache<int, Guid> cache,CancellationTokenSource source)
322+
{
323+
// On my machine, without SeqLock, this consistently fails below 100 iterations
324+
// on debug build, and below 1000 on release build
325+
for (int count = 0; count < 100_000; ++count)
326+
{
327+
cache.TryGet(1, out _);
328+
}
329+
330+
source.Cancel();
331+
}
332+
261333
private void RunIntegrityCheck()
262334
{
263335
new ConcurrentLruIntegrityChecker<int, string, LruItem<int, string>, LruPolicy<int, string>, TelemetryPolicy<int, string>>(this.lru).Validate();

BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,19 @@ public void WhenKeyExistsAddOrUpdateUpdatesExistingItem()
773773
value.Should().Be("2");
774774
}
775775

776+
[Fact]
777+
public void WhenKeyExistsAddOrUpdateGuidUpdatesExistingItem()
778+
{
779+
var lru2 = new ConcurrentLru<int, Guid>(1, capacity, EqualityComparer<int>.Default);
780+
781+
var b = new byte[8];
782+
lru2.AddOrUpdate(1, new Guid(1, 0, 0, b));
783+
lru2.AddOrUpdate(1, new Guid(2, 0, 0, b));
784+
785+
lru2.TryGet(1, out var value).Should().BeTrue();
786+
value.Should().Be(new Guid(2, 0, 0, b));
787+
}
788+
776789
[Fact]
777790
public void WhenKeyExistsAddOrUpdateDisposesOldValue()
778791
{

BitFaster.Caching.UnitTests/Lru/LruItemMemoryLayoutDumps.cs

Lines changed: 38 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,24 @@ public LruItemMemoryLayoutDumps(ITestOutputHelper testOutputHelper)
1616
}
1717

1818
//Type layout for 'LruItem`2'
19-
//Size: 24 bytes.Paddings: 6 bytes(%25 of empty space)
20-
//|===============================================|
21-
//| Object Header(8 bytes) |
22-
//|-----------------------------------------------|
23-
//| Method Table Ptr(8 bytes) |
24-
//|===============================================|
25-
//| 0-7: Object Key(8 bytes) |
26-
//|-----------------------------------------------|
27-
//| 8-15: Object<Value> k__BackingField(8 bytes) |
28-
//|-----------------------------------------------|
29-
//| 16: Boolean wasAccessed(1 byte) |
30-
//|-----------------------------------------------|
31-
//| 17: Boolean wasRemoved(1 byte) |
32-
//|-----------------------------------------------|
33-
//| 18-23: padding(6 bytes) |
34-
//|===============================================|
19+
//Size: 24 bytes. Paddings: 2 bytes (%8 of empty space)
20+
//|=====================================|
21+
//| Object Header (8 bytes) |
22+
//|-------------------------------------|
23+
//| Method Table Ptr (8 bytes) |
24+
//|=====================================|
25+
//| 0-7: Object data (8 bytes) |
26+
//|-------------------------------------|
27+
//| 8-15: Object Key (8 bytes) |
28+
//|-------------------------------------|
29+
//| 16-19: Int32 sequence (4 bytes) |
30+
//|-------------------------------------|
31+
//| 20: Boolean wasAccessed (1 byte) |
32+
//|-------------------------------------|
33+
//| 21: Boolean wasRemoved (1 byte) |
34+
//|-------------------------------------|
35+
//| 22-23: padding (2 bytes) |
36+
//|=====================================|
3537
[Fact]
3638
public void DumpLruItem()
3739
{
@@ -40,24 +42,26 @@ public void DumpLruItem()
4042
}
4143

4244
//Type layout for 'LongTickCountLruItem`2'
43-
//Size: 32 bytes.Paddings: 6 bytes(%18 of empty space)
44-
//|==================================================|
45-
//| Object Header(8 bytes) |
46-
//|--------------------------------------------------|
47-
//| Method Table Ptr(8 bytes) |
48-
//|==================================================|
49-
//| 0-7: Object Key(8 bytes) |
50-
//|--------------------------------------------------|
51-
//| 8-15: Object<Value> k__BackingField(8 bytes) |
52-
//|--------------------------------------------------|
53-
//| 16: Boolean wasAccessed(1 byte) |
54-
//|--------------------------------------------------|
55-
//| 17: Boolean wasRemoved(1 byte) |
56-
//|--------------------------------------------------|
57-
//| 18-23: padding(6 bytes) |
58-
//|--------------------------------------------------|
59-
//| 24-31: Int64<TickCount> k__BackingField(8 bytes) |
60-
//|==================================================|
45+
//Size: 32 bytes. Paddings: 2 bytes (%6 of empty space)
46+
//|===================================================|
47+
//| Object Header (8 bytes) |
48+
//|---------------------------------------------------|
49+
//| Method Table Ptr (8 bytes) |
50+
//|===================================================|
51+
//| 0-7: Object data (8 bytes) |
52+
//|---------------------------------------------------|
53+
//| 8-15: Object Key (8 bytes) |
54+
//|---------------------------------------------------|
55+
//| 16-19: Int32 sequence (4 bytes) |
56+
//|---------------------------------------------------|
57+
//| 20: Boolean wasAccessed (1 byte) |
58+
//|---------------------------------------------------|
59+
//| 21: Boolean wasRemoved (1 byte) |
60+
//|---------------------------------------------------|
61+
//| 22-23: padding (2 bytes) |
62+
//|---------------------------------------------------|
63+
//| 24-31: Int64 <TickCount>k__BackingField (8 bytes) |
64+
//|===================================================|
6165
[Fact]
6266
public void DumpLongTickCountLruItem()
6367
{

0 commit comments

Comments
 (0)