Skip to content

Commit 341ae2d

Browse files
committed
Replace LRU with implementation based on BitFaster.Caching's ConcurrentLru
1 parent 22a1b35 commit 341ae2d

24 files changed

+3666
-452
lines changed

src/Orleans.Core/Caching/ConcurrentLruCache.cs

Lines changed: 956 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#nullable enable
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace Orleans.Caching.Internal;
7+
8+
// Source: https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/CacheDebugView.cs
9+
[ExcludeFromCodeCoverage]
10+
internal sealed class CacheDebugView<K, V>
11+
where K : notnull
12+
{
13+
private readonly ICache<K, V> _cache;
14+
15+
public CacheDebugView(ICache<K, V> cache)
16+
{
17+
ArgumentNullException.ThrowIfNull(cache);
18+
_cache = cache;
19+
}
20+
21+
public KeyValuePair<K, V>[] Items
22+
{
23+
get
24+
{
25+
var items = new KeyValuePair<K, V>[_cache.Count];
26+
27+
var index = 0;
28+
foreach (var kvp in _cache)
29+
{
30+
items[index++] = kvp;
31+
}
32+
return items;
33+
}
34+
}
35+
36+
public ICacheMetrics? Metrics => Metrics;
37+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Diagnostics;
3+
4+
namespace Orleans.Caching.Internal;
5+
6+
/// <summary>
7+
/// A capacity partitioning scheme that favors frequently accessed items by allocating 80%
8+
/// capacity to the warm queue.
9+
/// </summary>
10+
// Source: https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/Lru/FavorWarmPartition.cs
11+
[DebuggerDisplay("{Hot}/{Warm}/{Cold}")]
12+
internal readonly struct CapacityPartition
13+
{
14+
/// <summary>
15+
/// Default to 80% capacity allocated to warm queue, 20% split equally for hot and cold.
16+
/// This favors frequently accessed items.
17+
/// </summary>
18+
public const double DefaultWarmRatio = 0.8;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the CapacityPartition class with the specified capacity and the default warm ratio.
22+
/// </summary>
23+
/// <param name="totalCapacity">The total capacity.</param>
24+
public CapacityPartition(int totalCapacity)
25+
: this(totalCapacity, DefaultWarmRatio)
26+
{
27+
}
28+
29+
/// <summary>
30+
/// Initializes a new instance of the CapacityPartition class with the specified capacity and warm ratio.
31+
/// </summary>
32+
/// <param name="totalCapacity">The total capacity.</param>
33+
/// <param name="warmRatio">The ratio of warm items to hot and cold items.</param>
34+
public CapacityPartition(int totalCapacity, double warmRatio)
35+
{
36+
ArgumentOutOfRangeException.ThrowIfLessThan(totalCapacity, 3);
37+
ArgumentOutOfRangeException.ThrowIfLessThan(warmRatio, 0.0);
38+
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(warmRatio, 1.0);
39+
40+
var (hot, warm, cold) = ComputeQueueCapacity(totalCapacity, warmRatio);
41+
Debug.Assert(cold >= 1);
42+
Debug.Assert(warm >= 1);
43+
Debug.Assert(hot >= 1);
44+
Hot = hot;
45+
Warm = warm;
46+
Cold = cold;
47+
}
48+
49+
public int Cold { get; }
50+
51+
public int Warm { get; }
52+
53+
public int Hot { get; }
54+
55+
private static (int hot, int warm, int cold) ComputeQueueCapacity(int capacity, double warmRatio)
56+
{
57+
var warm2 = (int)(capacity * warmRatio);
58+
var hot2 = (capacity - warm2) / 2;
59+
60+
if (hot2 < 1)
61+
{
62+
hot2 = 1;
63+
}
64+
65+
var cold2 = hot2;
66+
67+
var overflow = warm2 + hot2 + cold2 - capacity;
68+
warm2 -= overflow;
69+
70+
return (hot2, warm2, cold2);
71+
}
72+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace Orleans.Caching.Internal;
5+
6+
// Source: https://github.com/bitfaster/BitFaster.Caching/blob/5b2d64a1afcc251787fbe231c6967a62820fc93c/BitFaster.Caching/ConcurrentDictionarySize.cs
7+
internal static class ConcurrentDictionarySize
8+
{
9+
private static int NextPrimeGreaterThan(int min)
10+
{
11+
foreach (var prime in Primes)
12+
{
13+
if (prime > min)
14+
{
15+
return prime;
16+
}
17+
}
18+
19+
return min;
20+
}
21+
22+
/// <summary>
23+
/// Estimate the size of the ConcurrentDictionary constructor capacity arg to use for the given desired cache size.
24+
/// </summary>
25+
/// <remarks>
26+
/// To minimize collisions, ideal case is is for ConcurrentDictionary to have a prime number of buckets, and
27+
/// for the bucket count to be about 33% greater than the cache capacity (load factor of 0.75).
28+
/// See load factor here: https://en.wikipedia.org/wiki/Hash_table
29+
/// </remarks>
30+
/// <param name="desiredSize">The desired cache size</param>
31+
/// <returns>The estimated optimal ConcurrentDictionary capacity</returns>
32+
internal static int Estimate(int desiredSize)
33+
{
34+
// Size map entries are approx 4% apart in the worst case, so increase by 29% to target 33%.
35+
// In practice, this leads to the number of buckets being somewhere between 29% and 40% greater
36+
// than cache capacity.
37+
try
38+
{
39+
checked
40+
{
41+
desiredSize = (int)(desiredSize * 1.29);
42+
}
43+
44+
// When small, exact size hashtable to nearest larger prime number
45+
if (desiredSize < 197)
46+
{
47+
return NextPrimeGreaterThan(desiredSize);
48+
}
49+
50+
// When large, size to approx 10% of desired size to save memory. Initial value is chosen such
51+
// that 4x ConcurrentDictionary grow operations will select a prime number slightly larger
52+
// than desired size.
53+
foreach (var pair in SizeMap)
54+
{
55+
if (pair.Key > desiredSize)
56+
{
57+
return pair.Value;
58+
}
59+
}
60+
}
61+
catch (OverflowException)
62+
{
63+
// return largest
64+
}
65+
66+
// Use largest mapping: ConcurrentDictionary will resize to max array size after 4x grow calls.
67+
return SizeMap[^1].Value;
68+
}
69+
70+
#if NETSTANDARD2_0
71+
internal static int[] Primes = new int[] {
72+
#else
73+
private static ReadOnlySpan<int> Primes => new int[] {
74+
#endif
75+
3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
76+
1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
77+
17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
78+
187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
79+
1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
80+
};
81+
82+
#if NETSTANDARD2_0
83+
internal static KeyValuePair<int, int>[] SizeMap =
84+
#else
85+
private static ReadOnlySpan<KeyValuePair<int, int>> SizeMap =>
86+
#endif
87+
new KeyValuePair<int, int>[129]
88+
{
89+
new(197, 197),
90+
new(277, 137),
91+
new(331, 163),
92+
new(359, 179),
93+
new(397, 197),
94+
new(443, 221),
95+
new(499, 247),
96+
new(557, 137),
97+
new(599, 149),
98+
new(677, 167),
99+
new(719, 179),
100+
new(797, 197),
101+
new(839, 209),
102+
new(887, 221),
103+
new(1061, 131),
104+
new(1117, 137),
105+
new(1237, 151),
106+
new(1439, 179),
107+
new(1559, 193),
108+
new(1777, 221),
109+
new(2011, 247),
110+
new(2179, 269),
111+
new(2347, 289),
112+
new(2683, 331),
113+
new(2797, 347),
114+
new(3359, 419),
115+
new(3917, 487),
116+
new(4363, 541),
117+
new(4597, 571),
118+
new(5879, 733),
119+
new(7517, 937),
120+
new(8731, 1087),
121+
new(9839, 1229),
122+
new(17467, 2179),
123+
new(18397, 2297),
124+
new(20357, 2543),
125+
new(24317, 3037),
126+
new(25919, 3239),
127+
new(29759, 3719),
128+
new(31357, 3917),
129+
new(33599, 4199),
130+
new(38737, 4841),
131+
new(41117, 5137),
132+
new(48817, 6101),
133+
new(61819, 7723),
134+
new(72959, 9119),
135+
new(86011, 10747),
136+
new(129277, 16157),
137+
new(140797, 17597),
138+
new(164477, 20557),
139+
new(220411, 27547),
140+
new(233851, 29227),
141+
new(294397, 36797),
142+
new(314879, 39359),
143+
new(338683, 42331),
144+
new(389117, 48637),
145+
new(409597, 51197),
146+
new(436477, 54557),
147+
new(609277, 76157),
148+
new(651517, 81437),
149+
new(737279, 92159),
150+
new(849917, 106237),
151+
new(1118203, 139771),
152+
new(1269757, 158717),
153+
new(1440763, 180091),
154+
new(1576957, 197117),
155+
new(1684477, 210557),
156+
new(2293757, 286717),
157+
new(2544637, 318077),
158+
new(2666491, 333307),
159+
new(2846717, 355837),
160+
new(3368957, 421117),
161+
new(3543037, 442877),
162+
new(4472827, 559099),
163+
new(4710397, 588797),
164+
new(5038079, 629759),
165+
new(5763067, 720379),
166+
new(6072317, 759037),
167+
new(6594557, 824317),
168+
new(7913467, 989179),
169+
new(8257531, 1032187),
170+
new(9175037, 1146877),
171+
new(9633787, 1204219),
172+
new(10076159, 1259519),
173+
new(11386877, 1423357),
174+
new(14020603, 1752571),
175+
new(16056317, 2007037),
176+
new(19496957, 2437117),
177+
new(20848637, 2606077),
178+
new(24084479, 3010559),
179+
new(27934717, 3491837),
180+
new(29589499, 3698683),
181+
new(32788477, 4098557),
182+
new(36044797, 4505597),
183+
new(38051837, 4756477),
184+
new(43581437, 5447677),
185+
new(51814397, 6476797),
186+
new(56688637, 7086077),
187+
new(60948479, 7618559),
188+
new(69631997, 8703997),
189+
new(75366397, 9420797),
190+
new(78643199, 9830399),
191+
new(96337919, 12042239),
192+
new(106168319, 13271039),
193+
new(115671037, 14458877),
194+
new(132382717, 16547837),
195+
new(144179197, 18022397),
196+
new(165150719, 20643839),
197+
new(178257917, 22282237),
198+
new(188743679, 23592959),
199+
new(209715197, 26214397),
200+
new(254279677, 31784957),
201+
new(297271291, 37158907),
202+
new(314572799, 39321599),
203+
new(385351679, 48168959),
204+
new(453509117, 56688637),
205+
new(517472251, 64684027),
206+
new(644874239, 80609279),
207+
new(673710077, 84213757),
208+
new(770703359, 96337919),
209+
new(849346559, 106168319),
210+
new(903086077, 112885757),
211+
new(1145044987, 143130619),
212+
new(1233125371, 154140667),
213+
new(1321205759, 165150719),
214+
new(1394606077, 174325757),
215+
new(1635778559, 204472319),
216+
new(1855979519, 231997439),
217+
new(2003828731, 250478587),
218+
};
219+
}

0 commit comments

Comments
 (0)