Skip to content

Commit 16177f1

Browse files
committed
10-35% faster eviction job by getting ticks outside the loop
1 parent 532565b commit 16177f1

File tree

2 files changed

+37
-14
lines changed

2 files changed

+37
-14
lines changed

FastCache.Benchmarks/Program.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ public void GlobalSetup()
2626
}
2727
}
2828

29+
/*
30+
[Benchmark]
31+
public void EvictExpired()
32+
{
33+
_cache.EvictExpired();
34+
}
35+
36+
[Benchmark]
37+
public void EvictExpired2()
38+
{
39+
_cache.EvictExpiredOptimized();
40+
}
41+
*/
42+
2943
[Benchmark]
3044
public void DictionaryLookup()
3145
{

FastCache/FastCache.cs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,35 @@ public class FastCache<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>,
2121
/// <param name="cleanupJobInterval">cleanup interval in milliseconds, default is 10000</param>
2222
public FastCache(int cleanupJobInterval = 10000)
2323
{
24-
_cleanUpTimer = new Timer(_EvictExpired, null, cleanupJobInterval, cleanupJobInterval);
24+
_cleanUpTimer = new Timer(s => EvictExpired(), null, cleanupJobInterval, cleanupJobInterval);
25+
}
2526

26-
void _EvictExpired(object state)
27+
/// <summary>
28+
/// Cleans up expired items (dont' wait for the background job)
29+
/// </summary>
30+
public void EvictExpired()
31+
{
32+
//Eviction already started by another thread? forget it, lets move on
33+
if (Monitor.TryEnter(_cleanUpTimer)) //use the timer-object for our lock, it's local, private and instance-type, so its ok
2734
{
28-
//overlapped execution? forget it, lets move on
29-
if (Monitor.TryEnter(_cleanUpTimer)) //use the timer-object for our lock, it's local, private and instance-type, so its ok
35+
try
3036
{
31-
try
32-
{
33-
foreach (var p in _dict)
34-
{
35-
if (p.Value.IsExpired())
36-
_dict.TryRemove(p);
37-
}
38-
}
39-
finally
37+
//cache current tick count in a var to prevent calling it every iteration inside "IsExpired()" in a tight loop.
38+
//On a 10000-items cache this allows us to slice 30 microseconds: 330 vs 360 microseconds which is 10% faster
39+
//On a 50000-items cache it's even more: 2.057ms vs 2.817ms which is 35% faster!!
40+
//the bigger the cache the bigger the win
41+
var currTime = Environment.TickCount64;
42+
43+
foreach (var p in _dict)
4044
{
41-
Monitor.Exit(_cleanUpTimer);
45+
if (currTime > p.Value.TickCountWhenToKill) //instead of calling "p.Value.IsExpired" we're essentially doing the same thing manually
46+
_dict.TryRemove(p);
4247
}
4348
}
49+
finally
50+
{
51+
Monitor.Exit(_cleanUpTimer);
52+
}
4453
}
4554
}
4655

0 commit comments

Comments
 (0)