Skip to content

Commit ceac26c

Browse files
committed
back to grow only
1 parent b127c0d commit ceac26c

File tree

3 files changed

+39
-49
lines changed

3 files changed

+39
-49
lines changed

Tests/Cache.cs

Lines changed: 27 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ private struct Entry { internal int Bucket; internal int Next; internal K Key; i
1414
private sealed class Pending { internal required K Key; internal required Task<V> Value; internal Pending? Next; }
1515
private readonly Lock _lock = new();
1616
private int _count;
17-
private int _version;
1817
private Entry[] _entries;
1918
private Pending? _pending;
2019

2120
public Cache(int capacity = 2) => _entries = new Entry[capacity <= 2 ? 2 : BitOperations.RoundUpToPowerOf2((uint)capacity)];
2221

23-
public Cache(IEnumerable<KeyValuePair<K, V>> items) : this()
22+
public Cache(IEnumerable<KeyValuePair<K, V>> items, int capacity = 2) : this(capacity)
2423
{
2524
foreach (var (k, v) in items) AddOrUpdate(k, v);
2625
}
@@ -44,10 +43,7 @@ private void AddOrUpdate(K key, V value)
4443
entries[i].Next = entries[bucketIndex].Bucket - 1;
4544
entries[i].Key = key;
4645
entries[i].Value = value;
47-
entries[bucketIndex].Bucket = _count + 1;
48-
_entries = entries;
49-
_count++;
50-
_version++;
46+
_count = entries[bucketIndex].Bucket = _count + 1;
5147
}
5248

5349
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -63,16 +59,15 @@ private Entry[] Resize()
6359
newEntries[i].Value = oldEntries[i].Value;
6460
newEntries[bucketIndex].Bucket = ++i;
6561
}
66-
return newEntries;
62+
return _entries = newEntries;
6763
}
6864

69-
public ValueTask<V> GetOrAdd(K key, Func<K, Task<V>> factory)
70-
=> GetOrAdd(key, factory, static (k, f) => f(k));
65+
public ValueTask<V> GetOrAdd(K key, Func<K, Task<V>> factory) => GetOrAdd(key, factory, static (k, f) => f(k));
7166

7267
public ValueTask<V> GetOrAdd<TState>(K key, TState state, Func<K, TState, Task<V>> factory)
7368
{
7469
var hashCode = key.GetHashCode();
75-
var version = Volatile.Read(ref _version);
70+
var count = _count;
7671
var entries = Volatile.Read(ref _entries);
7772
var i = entries[hashCode & (entries.Length - 1)].Bucket - 1;
7873
while (i >= 0)
@@ -82,7 +77,7 @@ public ValueTask<V> GetOrAdd<TState>(K key, TState state, Func<K, TState, Task<V
8277
}
8378
lock (_lock)
8479
{
85-
if (_version != version)
80+
if (_count != count)
8681
{
8782
entries = _entries;
8883
i = entries[hashCode & (entries.Length - 1)].Bucket - 1;
@@ -96,39 +91,30 @@ public ValueTask<V> GetOrAdd<TState>(K key, TState state, Func<K, TState, Task<V
9691
}
9792
}
9893

99-
public Task<V> Update(K key, Func<K, Task<V>> factory)
100-
=> Update(key, factory, static (k, f) => f(k));
94+
public Task<V> Update(K key, Func<K, Task<V>> factory) => Update(key, factory, static (k, f) => f(k));
10195

10296
public Task<V> Update<TState>(K key, TState state, Func<K, TState, Task<V>> factory)
10397
{
10498
lock (_lock)
10599
return GetOrAddPending(key, state, factory).Value;
106100
}
107101

108-
public void Compact(Func<KeyValuePair<K, V>, bool> keep)
102+
public async Task<Cache<K, V>> Compact(Func<KeyValuePair<K, V>, bool> keep)
109103
{
110-
lock (_lock)
104+
while (true)
111105
{
112-
var oldCount = _count;
113-
var newCount = this.Count(keep);
114-
if (newCount == oldCount) return;
115-
var newEntries = new Entry[newCount < 2 ? 2 : BitOperations.RoundUpToPowerOf2((uint)newCount)];
116-
var oldEntries = _entries;
117-
int newIndex = 0;
118-
for (int i = 0; i < oldCount; i++)
106+
var pending = _pending;
107+
while (pending is not null)
119108
{
120-
if (keep(new(oldEntries[i].Key, oldEntries[i].Value)))
121-
{
122-
var bucketIndex = oldEntries[i].Key.GetHashCode() & (newEntries.Length - 1);
123-
newEntries[newIndex].Next = newEntries[bucketIndex].Bucket - 1;
124-
newEntries[newIndex].Key = oldEntries[i].Key;
125-
newEntries[newIndex].Value = oldEntries[i].Value;
126-
newEntries[bucketIndex].Bucket = ++newIndex;
127-
}
109+
try { await pending.Value; }
110+
catch { }
111+
pending = pending.Next;
128112
}
129-
_count = newIndex;
130-
_entries = newEntries;
131-
_version++;
113+
var newCount = this.Count(keep);
114+
var count = _count;
115+
if (count == newCount) return this;
116+
var newCache = new Cache<K, V>(this.Where(keep), newCount);
117+
if (_count == count && _pending is null) return newCache;
132118
}
133119
}
134120

@@ -190,17 +176,8 @@ private void RemovePending(Pending remove)
190176

191177
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
192178
{
193-
var version = Volatile.Read(ref _version);
194-
var count = _count;
195-
var entries = _entries;
196-
if (version != Volatile.Read(ref _version))
197-
lock (_lock)
198-
{
199-
count = _count;
200-
entries = _entries;
201-
}
202-
for (int i = 0; i < count; i++)
203-
yield return new(entries[i].Key, entries[i].Value);
179+
for (int i = 0; i < _count; i++)
180+
yield return new(_entries[i].Key, _entries[i].Value);
204181
}
205182

206183
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
@@ -230,7 +207,7 @@ public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
230207
/// </para></summary>
231208
public sealed class RefreshingCache<K, V> : IDisposable where K : IEquatable<K>
232209
{
233-
private readonly Cache<K, (long Timestamp, V Value)> _cache = new();
210+
private Cache<K, (long Timestamp, V Value)> _cache = new();
234211
private readonly long _durationTicks, _eagerRefreshTicks;
235212
private readonly TimeSpan _softTimeout;
236213
private readonly Timer? _timer;
@@ -245,8 +222,11 @@ public RefreshingCache(TimeSpan duration, double eagerRefreshRatio = 0.5, TimeSp
245222

246223
private void Cleanup(object? removeTicks)
247224
{
248-
var removeTimestamp = Stopwatch.GetTimestamp() - (long)removeTicks!;
249-
_cache.Compact(e => e.Value.Timestamp >= removeTimestamp);
225+
_ = Task.Run(async () =>
226+
{
227+
var removeTimestamp = Stopwatch.GetTimestamp() - (long)removeTicks!;
228+
_cache = await _cache.Compact(e => e.Value.Timestamp >= removeTimestamp);
229+
});
250230
}
251231

252232
private static async Task<(long, V)> CallFactory(K key, Func<K, Task<V>> factory)

Tests/CacheTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public async Task Cache_Compact_RemovesEntries()
182182
for (int i = 0; i < 10; i++)
183183
await cache.GetOrAdd(i, k => Task.FromResult(k * 10));
184184
await Assert.That(cache.Count).IsEqualTo(10);
185-
cache.Compact(e => e.Key % 2 == 0);
185+
cache = await cache.Compact(e => e.Key % 2 == 0);
186186
await Assert.That(cache.Count).IsEqualTo(5);
187187
// Kept entries are still accessible
188188
await Assert.That(await cache.GetOrAdd(0, _ => Task.FromResult(-1))).IsEqualTo(0);

Tests/CheckTests.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,10 +297,20 @@ public void SampleParallelModel_ConcurrentDictionary()
297297
}
298298

299299
[Test]
300-
public void Equality()
300+
public void Equality_Int()
301301
{
302302
Check.Equality(Gen.Int);
303+
}
304+
305+
[Test]
306+
public void Equality_Double()
307+
{
303308
Check.Equality(Gen.Double);
309+
}
310+
311+
[Test]
312+
public void Equality_String()
313+
{
304314
Check.Equality(Gen.String);
305315
}
306316

0 commit comments

Comments
 (0)