Skip to content

Commit 27de998

Browse files
authored
ConcurrentLfu (#183)
* ConcurrentLfu * test * fix * tests * fix bounds * notes * cleanup * boundedbuffer * basic tests * more tests * scheduler * cleanup * trim with races * background * don't hang * stable background threads * stable tests * test cancel * test full buffer * cleanup * fix rename * stability workaround * tests * lower threshold * thru put * simplify * thresholds * test logic * don't drop after addupdate/remove * cleanup bench * not interlocked * conditional inc * clean header * big buffer
1 parent 62aa21a commit 27de998

32 files changed

+2711
-10
lines changed

BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<ItemGroup>
1717
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
18+
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.1" />
1819
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
1920
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
2021
<PackageReference Include="System.Runtime.Caching" Version="6.0.0" />
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Diagnosers;
3+
using BenchmarkDotNet.Jobs;
4+
using BitFaster.Caching;
5+
using BitFaster.Caching.Benchmarks.Lru;
6+
using BitFaster.Caching.Lfu;
7+
using BitFaster.Caching.Lru;
8+
using BitFaster.Caching.Scheduler;
9+
using Microsoft.Extensions.Caching.Memory;
10+
using System;
11+
using System.Collections.Concurrent;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using System.Text;
15+
using System.Threading.Tasks;
16+
17+
namespace BitFaster.Caching.Benchmarks
18+
{
19+
[SimpleJob(RuntimeMoniker.Net48)]
20+
[SimpleJob(RuntimeMoniker.Net60)]
21+
[DisassemblyDiagnoser(printSource: true, maxDepth: 5)]
22+
[MemoryDiagnoser]
23+
// [HardwareCounters(HardwareCounter.LlcMisses, HardwareCounter.CacheMisses)] // Requires Admin https://adamsitnik.com/Hardware-Counters-Diagnoser/
24+
// [ThreadingDiagnoser] // Requires .NET Core
25+
public class LfuJustGetOrAdd
26+
{
27+
private static readonly ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>(8, 9, EqualityComparer<int>.Default);
28+
29+
private static readonly BackgroundThreadScheduler background = new BackgroundThreadScheduler();
30+
private static readonly ConcurrentLfu<int, int> concurrentLfu = new ConcurrentLfu<int, int>(9, background);
31+
32+
private static readonly ConcurrentLfu<int, int> concurrentLfuFore = new ConcurrentLfu<int, int>(9, new ForegroundScheduler());
33+
private static readonly ConcurrentLfu<int, int> concurrentLfuTp = new ConcurrentLfu<int, int>(9, new ThreadPoolScheduler());
34+
private static readonly ConcurrentLfu<int, int> concurrentLfuNull = new ConcurrentLfu<int, int>(9, new NullScheduler());
35+
36+
[GlobalSetup]
37+
public void GlobalSetup()
38+
{
39+
}
40+
41+
[GlobalCleanup]
42+
public void GlobalCleanup()
43+
{
44+
background.Dispose();
45+
}
46+
47+
[Benchmark(Baseline = true)]
48+
public void ConcurrentDictionary()
49+
{
50+
Func<int, int> func = x => x;
51+
dictionary.GetOrAdd(1, func);
52+
}
53+
54+
[Benchmark()]
55+
public void ConcurrentLfuBackground()
56+
{
57+
Func<int, int> func = x => x;
58+
concurrentLfu.GetOrAdd(1, func);
59+
}
60+
61+
[Benchmark()]
62+
public void ConcurrentLfuForeround()
63+
{
64+
Func<int, int> func = x => x;
65+
concurrentLfuFore.GetOrAdd(1, func);
66+
}
67+
68+
[Benchmark()]
69+
public void ConcurrentLfuThreadPool()
70+
{
71+
Func<int, int> func = x => x;
72+
concurrentLfuTp.GetOrAdd(1, func);
73+
}
74+
75+
[Benchmark()]
76+
public void ConcurrentLfuNull()
77+
{
78+
Func<int, int> func = x => x;
79+
concurrentLfuNull.GetOrAdd(1, func);
80+
}
81+
}
82+
}

BitFaster.Caching.Benchmarks/Lru/LruJustGetOrAdd.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Diagnosers;
23
using BenchmarkDotNet.Jobs;
34
using BitFaster.Caching;
45
using BitFaster.Caching.Benchmarks.Lru;
6+
using BitFaster.Caching.Lfu;
57
using BitFaster.Caching.Lru;
8+
using BitFaster.Caching.Scheduler;
69
using Microsoft.Extensions.Caching.Memory;
710
using System;
811
using System.Collections.Concurrent;
@@ -34,6 +37,8 @@ namespace BitFaster.Caching.Benchmarks
3437
[SimpleJob(RuntimeMoniker.Net60)]
3538
[DisassemblyDiagnoser(printSource: true, maxDepth: 5)]
3639
[MemoryDiagnoser]
40+
// [HardwareCounters(HardwareCounter.LlcMisses, HardwareCounter.CacheMisses)] // Requires Admin https://adamsitnik.com/Hardware-Counters-Diagnoser/
41+
// [ThreadingDiagnoser] // Requires .NET Core
3742
public class LruJustGetOrAdd
3843
{
3944
private static readonly ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>(8, 9, EqualityComparer<int>.Default);
@@ -46,6 +51,10 @@ public class LruJustGetOrAdd
4651

4752
private static readonly ICache<int, int> atomicFastLru = new ConcurrentLruBuilder<int, int>().WithConcurrencyLevel(8).WithCapacity(9).WithAtomicGetOrAdd().Build();
4853

54+
private static readonly BackgroundThreadScheduler background = new BackgroundThreadScheduler();
55+
private static readonly ConcurrentLfu<int, int> concurrentLfu = new ConcurrentLfu<int, int>(9, background);
56+
57+
4958
private static readonly int key = 1;
5059
private static System.Runtime.Caching.MemoryCache memoryCache = System.Runtime.Caching.MemoryCache.Default;
5160

@@ -59,6 +68,12 @@ public void GlobalSetup()
5968
exMemoryCache.Set(key, "test");
6069
}
6170

71+
[GlobalCleanup]
72+
public void GlobalCleanup()
73+
{
74+
background.Dispose();
75+
}
76+
6277
[Benchmark(Baseline = true)]
6378
public void ConcurrentDictionary()
6479
{
@@ -101,6 +116,13 @@ public void ConcurrentTLru()
101116
concurrentTlru.GetOrAdd(1, func);
102117
}
103118

119+
[Benchmark()]
120+
public void ConcurrentLfu()
121+
{
122+
Func<int, int> func = x => x;
123+
concurrentLfu.GetOrAdd(1, func);
124+
}
125+
104126
[Benchmark()]
105127
public void ClassicLru()
106128
{

BitFaster.Caching.HitRateAnalysis/Arc/Analysis.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Linq;
66
using System.Text;
77
using System.Threading.Tasks;
8+
using BitFaster.Caching.Lfu;
89
using BitFaster.Caching.Lru;
10+
using BitFaster.Caching.Scheduler;
911
using ConsoleTables;
1012
using CsvHelper;
1113

@@ -15,23 +17,28 @@ public class Analysis
1517
{
1618
private readonly ConcurrentLru<long, int> concurrentLru;
1719
private readonly ClassicLru<long, int> classicLru;
20+
private readonly ConcurrentLfu<long, int> concurrentLfu;
1821

1922
public Analysis(int cacheSize)
2023
{
2124
concurrentLru = new ConcurrentLru<long, int>(1, cacheSize, EqualityComparer<long>.Default);
2225
classicLru = new ClassicLru<long, int>(1, cacheSize, EqualityComparer<long>.Default);
26+
concurrentLfu = new ConcurrentLfu<long, int>(cacheSize, new ForegroundScheduler());
2327
}
2428

2529
public int CacheSize => concurrentLru.Capacity;
2630

31+
public double ClassicLruHitRate => classicLru.Metrics.Value.HitRatio * 100;
32+
2733
public double ConcurrentLruHitRate => concurrentLru.Metrics.Value.HitRatio * 100;
2834

29-
public double ClassicLruHitRate => classicLru.Metrics.Value.HitRatio * 100;
35+
public double ConcurrentLfuHitRate => concurrentLfu.Metrics.Value.HitRatio * 100;
3036

3137
public void TestKey(long key)
3238
{
3339
concurrentLru.GetOrAdd(key, u => 1);
3440
classicLru.GetOrAdd(key, u => 1);
41+
concurrentLfu.GetOrAdd(key, u => 1);
3542
}
3643

3744
public static void WriteToFile(string path, IEnumerable<Analysis> results)

BitFaster.Caching.HitRateAnalysis/Glimpse/Analysis.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Linq;
66
using System.Text;
77
using System.Threading.Tasks;
8+
using BitFaster.Caching.Lfu;
89
using BitFaster.Caching.Lru;
10+
using BitFaster.Caching.Scheduler;
911
using ConsoleTables;
1012
using CsvHelper;
1113

@@ -15,23 +17,28 @@ public class Analysis
1517
{
1618
private readonly ConcurrentLru<long, int> concurrentLru;
1719
private readonly ClassicLru<long, int> classicLru;
20+
private readonly ConcurrentLfu<long, int> concurrentLfu;
1821

1922
public Analysis(int cacheSize)
2023
{
2124
this.concurrentLru = new ConcurrentLru<long, int>(1, cacheSize, EqualityComparer<long>.Default);
2225
this.classicLru = new ClassicLru<long, int>(1, cacheSize, EqualityComparer<long>.Default);
26+
concurrentLfu = new ConcurrentLfu<long, int>(cacheSize, new ForegroundScheduler());
2327
}
2428

2529
public int CacheSize => this.concurrentLru.Capacity;
2630

31+
public double ClassicLruHitRate => this.classicLru.Metrics.Value.HitRatio * 100;
32+
2733
public double ConcurrentLruHitRate => this.concurrentLru.Metrics.Value.HitRatio * 100;
2834

29-
public double ClassicLruHitRate => this.classicLru.Metrics.Value.HitRatio * 100;
35+
public double ConcurrentLfuHitRate => concurrentLfu.Metrics.Value.HitRatio * 100;
3036

3137
public void TestKey(long key)
3238
{
3339
this.concurrentLru.GetOrAdd(key, u => 1);
3440
this.classicLru.GetOrAdd(key, u => 1);
41+
concurrentLfu.GetOrAdd(key, u => 1);
3542
}
3643

3744
public static void WriteToFile(string path, IEnumerable<Analysis> results)

BitFaster.Caching.HitRateAnalysis/Glimpse/Runner.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public static async Task Run()
2727
a.TestKey(key);
2828
}
2929

30-
if (count++ % 100000 == 0)
30+
if (++count % 1000 == 0)
3131
{
3232
Console.WriteLine($"Processed {count} keys...");
3333
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13+
<PackageReference Include="ConsoleTables" Version="2.4.2" />
1314
<PackageReference Include="CsvHelper" Version="28.0.1" />
1415
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
1516
</ItemGroup>

BitFaster.Caching.ThroughputAnalysis/Program.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
using System.Reflection.Metadata.Ecma335;
1010
using System.Threading;
1111
using System.Threading.Tasks;
12+
using BitFaster.Caching.Lfu;
1213
using BitFaster.Caching.Lru;
14+
using BitFaster.Caching.Scheduler;
15+
using ConsoleTables;
1316
using CsvHelper;
1417
using MathNet.Numerics.Distributions;
1518

@@ -48,10 +51,14 @@ static void Main(string[] args)
4851
resultTable.Columns.Add(tc.ToString());
4952
}
5053

51-
DataRow concurrentLru = resultTable.NewRow();
54+
5255
DataRow classicLru = resultTable.NewRow();
53-
concurrentLru["Class"] = "concurrentLru";
56+
DataRow concurrentLru = resultTable.NewRow();
57+
DataRow concurrentLfu = resultTable.NewRow();
58+
5459
classicLru["Class"] = "classicLru";
60+
concurrentLru["Class"] = "concurrentLru";
61+
concurrentLfu["Class"] = "concurrentLfu";
5562

5663
foreach (int tc in threadCount)
5764
{
@@ -61,26 +68,42 @@ static void Main(string[] args)
6168

6269
for (int i = 0; i < warmup + runs; i++)
6370
{
64-
results[i] = MeasureThroughput(new ConcurrentLru<int, int>(tc, capacity, EqualityComparer<int>.Default), tc);
71+
results[i] = MeasureThroughput(new ClassicLru<int, int>(tc, capacity, EqualityComparer<int>.Default), tc);
6572
}
6673
double avg = AverageLast(results, runs) / 1000000;
74+
Console.WriteLine($"ClassicLru ({tc}) {avg} million ops/sec");
75+
classicLru[tc.ToString()] = avg.ToString();
76+
77+
for (int i = 0; i < warmup + runs; i++)
78+
{
79+
results[i] = MeasureThroughput(new ConcurrentLru<int, int>(tc, capacity, EqualityComparer<int>.Default), tc);
80+
}
81+
avg = AverageLast(results, runs) / 1000000;
6782
Console.WriteLine($"ConcurrLru ({tc}) {avg} million ops/sec");
6883
concurrentLru[tc.ToString()] = avg.ToString();
6984

7085
for (int i = 0; i < warmup + runs; i++)
7186
{
72-
results[i] = MeasureThroughput(new ClassicLru<int, int>(tc, capacity, EqualityComparer<int>.Default), tc);
87+
var scheduler = new BackgroundThreadScheduler();
88+
results[i] = MeasureThroughput(new ConcurrentLfu<int, int>(capacity, scheduler), tc);
89+
scheduler.Dispose();
7390
}
7491
avg = AverageLast(results, runs) / 1000000;
75-
Console.WriteLine($"ClassicLru ({tc}) {avg} million ops/sec");
76-
classicLru[tc.ToString()] = avg.ToString();
92+
Console.WriteLine($"ConcurrLfu ({tc}) {avg} million ops/sec");
93+
concurrentLfu[tc.ToString()] = avg.ToString();
7794
}
7895

79-
resultTable.Rows.Add(concurrentLru);
8096
resultTable.Rows.Add(classicLru);
97+
resultTable.Rows.Add(concurrentLru);
98+
resultTable.Rows.Add(concurrentLfu);
8199

82100
ExportCsv(resultTable);
83101

102+
//ConsoleTable
103+
// .From(resultTable)
104+
// .Configure(o => o.NumberAlignment = Alignment.Right)
105+
// .Write(Format.MarkDown);
106+
84107
Console.WriteLine("Done.");
85108
}
86109

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using FluentAssertions;
7+
using Xunit;
8+
9+
namespace BitFaster.Caching.UnitTests
10+
{
11+
public class BitOpsTests
12+
{
13+
[Theory]
14+
[InlineData(3, 4)]
15+
[InlineData(7, 8)]
16+
[InlineData(15, 16)]
17+
[InlineData(536870913, 1073741824)]
18+
public void IntCeilingPowerOfTwo(int input, int power)
19+
{
20+
BitOps.CeilingPowerOfTwo(input).Should().Be(power);
21+
}
22+
23+
[Theory]
24+
[InlineData(3, 4)]
25+
[InlineData(7, 8)]
26+
[InlineData(15, 16)]
27+
[InlineData(536870913, 1073741824)]
28+
29+
public void UIntCeilingPowerOfTwo(uint input, uint power)
30+
{
31+
BitOps.CeilingPowerOfTwo(input).Should().Be(power);
32+
}
33+
34+
[Fact]
35+
public void IntBitCount()
36+
{
37+
BitOps.BitCount(666).Should().Be(5);
38+
}
39+
40+
[Fact]
41+
public void LongtBitCount()
42+
{
43+
BitOps.BitCount(666L).Should().Be(5);
44+
}
45+
46+
[Fact]
47+
public void ULongtBitCount()
48+
{
49+
BitOps.BitCount(666UL).Should().Be(5);
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)