Skip to content

Commit 6118f21

Browse files
committed
No closures in GetOrAdd - almost 2X FASTER!!
1 parent aeefcf7 commit 6118f21

File tree

1 file changed

+19
-19
lines changed

1 file changed

+19
-19
lines changed

FastCache/FastCache.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections;
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
5+
using System.Runtime.CompilerServices;
56
using System.Threading;
67
using System.Threading.Tasks;
78

@@ -230,24 +231,17 @@ public bool TryAdd(TKey key, TValue value, TimeSpan ttl)
230231
return _dict.TryUpdate(key, new TtlValue(value, ttl), existing);
231232
}
232233

233-
private TValue GetOrAddCore(TKey key, Func<TValue> valueFactory, TimeSpan ttl)
234+
private TValue GetOrAddCore<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TArg factoryArg, long ttlMs)
234235
{
235-
bool wasAdded = false; //flag to indicate "add vs get". TODO: wrap in ref type some day to avoid captures/closures
236236
var ttlValue = _dict.GetOrAdd(
237237
key,
238-
(_) =>
239-
{
240-
wasAdded = true;
241-
return new TtlValue(valueFactory(), ttl);
242-
});
238+
static (k, arg) => new TtlValue(arg.valueFactory(k, arg.factoryArg), arg.ttlMs),
239+
(valueFactory, factoryArg, ttlMs));
243240

244241
//if the item is expired, update value and TTL
245242
//since TtlValue is a reference type we can update its properties in-place, instead of removing and re-adding to the dictionary (extra lookups)
246-
if (!wasAdded) //performance hack: skip expiration check if a brand item was just added
247-
{
248-
if (ttlValue.ModifyIfExpired(valueFactory, ttl))
249-
OnEviction(key);
250-
}
243+
if (ttlValue.ModifyIfExpired(static args => args.valueFactory(args.key, args.factoryArg), (valueFactory, key, factoryArg), ttlMs))
244+
OnEviction(key);
251245

252246
return ttlValue.Value;
253247
}
@@ -259,7 +253,7 @@ private TValue GetOrAddCore(TKey key, Func<TValue> valueFactory, TimeSpan ttl)
259253
/// <param name="valueFactory">The factory function used to generate the item for the key</param>
260254
/// <param name="ttl">TTL of the item</param>
261255
public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory, TimeSpan ttl)
262-
=> GetOrAddCore(key, () => valueFactory(key), ttl);
256+
=> GetOrAddCore(key, static (k, f) => f(k), valueFactory, (long)ttl.TotalMilliseconds);
263257

264258
/// <summary>
265259
/// Adds a key/value pair by using the specified function if the key does not already exist, or returns the existing value if the key exists.
@@ -269,7 +263,7 @@ public TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory, TimeSpan ttl)
269263
/// <param name="ttl">TTL of the item</param>
270264
/// <param name="factoryArgument">Argument value to pass into valueFactory</param>
271265
public TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, TimeSpan ttl, TArg factoryArgument)
272-
=> GetOrAddCore(key, () => valueFactory(key, factoryArgument), ttl);
266+
=> GetOrAddCore(key, static (k, args) => args.valueFactory(k, args.factoryArgument), (valueFactory, factoryArgument), (long)ttl.TotalMilliseconds);
273267

274268
/// <summary>
275269
/// Adds a key/value pair by using the specified function if the key does not already exist, or returns the existing value if the key exists.
@@ -278,7 +272,7 @@ public TValue GetOrAdd<TArg>(TKey key, Func<TKey, TArg, TValue> valueFactory, Ti
278272
/// <param name="value">The value to add</param>
279273
/// <param name="ttl">TTL of the item</param>
280274
public TValue GetOrAdd(TKey key, TValue value, TimeSpan ttl)
281-
=> GetOrAddCore(key, () => value, ttl);
275+
=> GetOrAddCore(key, static (_, v) => v, value, (long)ttl.TotalMilliseconds);
282276

283277
/// <summary>
284278
/// Tries to remove item with the specified key
@@ -355,6 +349,12 @@ private class TtlValue
355349
public TValue Value { get; private set; }
356350
private long TickCountWhenToKill;
357351

352+
public TtlValue(TValue value, long ttlMs)
353+
{
354+
Value = value;
355+
TickCountWhenToKill = Environment.TickCount64 + ttlMs;
356+
}
357+
358358
public TtlValue(TValue value, TimeSpan ttl)
359359
{
360360
Value = value;
@@ -369,16 +369,16 @@ public TtlValue(TValue value, TimeSpan ttl)
369369
public bool IsExpired(long currTime) => currTime > TickCountWhenToKill;
370370

371371
/// <summary>
372-
/// Updates the value and TTL only if the item is expired
372+
/// Updates the value and TTL only if the item is expired, using a factory function
373373
/// </summary>
374374
/// <returns>True if the item expired and was updated, otherwise false</returns>
375-
public bool ModifyIfExpired(Func<TValue> newValueFactory, TimeSpan newTtl)
375+
public bool ModifyIfExpired<TArg>(Func<TArg, TValue> newValueFactory, TArg arg, long ttlMs)
376376
{
377377
var ticks = Environment.TickCount64; //save to a var to prevent multiple calls to Environment.TickCount64
378378
if (IsExpired(ticks)) //if expired - update the value and TTL
379379
{
380-
TickCountWhenToKill = ticks + (long)newTtl.TotalMilliseconds; //update the expiration time first for better concurrency
381-
Value = newValueFactory();
380+
TickCountWhenToKill = ticks + ttlMs; //update the expiration time first for better concurrency
381+
Value = newValueFactory(arg);
382382
return true;
383383
}
384384
return false;

0 commit comments

Comments
 (0)