Skip to content

Commit 34fec8b

Browse files
authored
TLru using stopwatch (#31)
* stopwatch * fix tests * use static * longer ttl for unit test
1 parent 237b0dd commit 34fec8b

File tree

10 files changed

+317
-9
lines changed

10 files changed

+317
-9
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using BenchmarkDotNet.Attributes;
5+
using BitFaster.Caching.Lru;
6+
7+
namespace BitFaster.Caching.Benchmarks.Lru
8+
{
9+
public class TLruTimeBenchmark
10+
{
11+
private static readonly TemplateConcurrentLru<int, int, TimeStampedLruItem<int, int>, TLruDateTimePolicy<int, int>, NullHitCounter> dateTimeTLru
12+
= new TemplateConcurrentLru<int, int, TimeStampedLruItem<int, int>, TLruDateTimePolicy<int, int>, NullHitCounter>
13+
(1, 3, EqualityComparer<int>.Default, new TLruDateTimePolicy<int, int>(TimeSpan.FromSeconds(1)), new NullHitCounter());
14+
15+
private static readonly TemplateConcurrentLru<int, int, TickCountLruItem<int, int>, TLruTicksPolicy<int, int>, NullHitCounter> tickCountTLru
16+
= new TemplateConcurrentLru<int, int, TickCountLruItem<int, int>, TLruTicksPolicy<int, int>, NullHitCounter>
17+
(1, 3, EqualityComparer<int>.Default, new TLruTicksPolicy<int, int>(TimeSpan.FromSeconds(1)), new NullHitCounter());
18+
19+
private static readonly TemplateConcurrentLru<int, int, LongTickCountLruItem<int, int>, TLruLongTicksPolicy<int, int>, NullHitCounter> stopwatchTLru
20+
= new TemplateConcurrentLru<int, int, LongTickCountLruItem<int, int>, TLruLongTicksPolicy<int, int>, NullHitCounter>
21+
(1, 3, EqualityComparer<int>.Default, new TLruLongTicksPolicy<int, int>(TimeSpan.FromSeconds(1)), new NullHitCounter());
22+
23+
[Benchmark(Baseline = true)]
24+
public void DateTimeUtcNow()
25+
{
26+
Func<int, int> func = x => x;
27+
dateTimeTLru.GetOrAdd(1, func);
28+
}
29+
30+
[Benchmark()]
31+
public void EnvironmentTickCount()
32+
{
33+
Func<int, int> func = x => x;
34+
tickCountTLru.GetOrAdd(1, func);
35+
}
36+
37+
[Benchmark()]
38+
public void StopWatchGetTimestamp()
39+
{
40+
Func<int, int> func = x => x;
41+
stopwatchTLru.GetOrAdd(1, func);
42+
}
43+
}
44+
}

BitFaster.Caching.Benchmarks/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Program
1515
static void Main(string[] args)
1616
{
1717
var summary = BenchmarkRunner
18-
.Run<LruCycle>(ManualConfig.Create(DefaultConfig.Instance)
18+
.Run<TLruTimeBenchmark>(ManualConfig.Create(DefaultConfig.Instance)
1919
.AddJob(Job.RyuJitX64));
2020
}
2121
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Text;
5+
using BenchmarkDotNet.Attributes;
6+
7+
namespace BitFaster.Caching.Benchmarks
8+
{
9+
public class TimeBenchmarks
10+
{
11+
private static readonly Stopwatch sw = Stopwatch.StartNew();
12+
13+
[Benchmark(Baseline = true)]
14+
public DateTime DateTimeUtcNow()
15+
{
16+
return DateTime.UtcNow;
17+
}
18+
19+
[Benchmark()]
20+
public int EnvironmentTickCount()
21+
{
22+
return Environment.TickCount;
23+
}
24+
25+
[Benchmark()]
26+
public long StopWatchGetElapsed()
27+
{
28+
return sw.ElapsedTicks;
29+
}
30+
}
31+
}

BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public void WhenComparerIsNullCtorThrows()
4343
[Fact]
4444
public void ConstructAddAndRetrieveWithDefaultCtorReturnsValue()
4545
{
46-
var x = new ConcurrentTLru<int, int>(3);
46+
var x = new ClassicLru<int, int>(3);
4747

4848
x.GetOrAdd(1, k => k).Should().Be(1);
4949
}

BitFaster.Caching.UnitTests/Lru/ConcurrentTLruTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace BitFaster.Caching.UnitTests.Lru
1010
{
1111
public class ConcurrentTLruTests
1212
{
13-
private readonly TimeSpan timeToLive = TimeSpan.FromMilliseconds(10);
13+
private readonly TimeSpan timeToLive = TimeSpan.FromSeconds(1);
1414
private const int capacity = 9;
1515
private ConcurrentTLru<int, string> lru;
1616

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using FluentAssertions;
2+
using FluentAssertions.Extensions;
3+
using BitFaster.Caching.Lru;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using Xunit;
10+
using System.Diagnostics;
11+
12+
namespace BitFaster.Caching.UnitTests.Lru
13+
{
14+
public class TLruLongTicksPolicyTests
15+
{
16+
private readonly TLruLongTicksPolicy<int, int> policy = new TLruLongTicksPolicy<int, int>(TimeSpan.FromSeconds(10));
17+
18+
[Fact]
19+
public void CreateItemInitializesKeyAndValue()
20+
{
21+
var item = this.policy.CreateItem(1, 2);
22+
23+
item.Key.Should().Be(1);
24+
item.Value.Should().Be(2);
25+
}
26+
27+
[Fact]
28+
public void CreateItemInitializesTimestampToNow()
29+
{
30+
var item = this.policy.CreateItem(1, 2);
31+
32+
// seconds = ticks / Stopwatch.Frequency
33+
ulong epsilon = (ulong)(TimeSpan.FromMilliseconds(20).TotalSeconds * Stopwatch.Frequency);
34+
item.TickCount.Should().BeCloseTo(Stopwatch.GetTimestamp(), epsilon);
35+
}
36+
37+
[Fact]
38+
public void TouchUpdatesItemWasAccessed()
39+
{
40+
var item = this.policy.CreateItem(1, 2);
41+
item.WasAccessed = false;
42+
43+
this.policy.Touch(item);
44+
45+
item.WasAccessed.Should().BeTrue();
46+
}
47+
48+
[Fact]
49+
public void WhenItemIsExpiredShouldDiscardIsTrue()
50+
{
51+
var item = this.policy.CreateItem(1, 2);
52+
item.TickCount = Stopwatch.GetTimestamp() - TimeSpan.FromSeconds(11).Ticks;
53+
54+
this.policy.ShouldDiscard(item).Should().BeTrue();
55+
}
56+
57+
[Fact]
58+
public void WhenItemIsNotExpiredShouldDiscardIsFalse()
59+
{
60+
var item = this.policy.CreateItem(1, 2);
61+
item.TickCount = Stopwatch.GetTimestamp() - TimeSpan.FromSeconds(9).Ticks;
62+
63+
this.policy.ShouldDiscard(item).Should().BeFalse();
64+
}
65+
66+
[Theory]
67+
[InlineData(false, true, ItemDestination.Remove)]
68+
[InlineData(true, true, ItemDestination.Remove)]
69+
[InlineData(true, false, ItemDestination.Warm)]
70+
[InlineData(false, false, ItemDestination.Cold)]
71+
public void RouteHot(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
72+
{
73+
var item = CreateItem(wasAccessed, isExpired);
74+
75+
this.policy.RouteHot(item).Should().Be(expectedDestination);
76+
}
77+
78+
[Theory]
79+
[InlineData(false, true, ItemDestination.Remove)]
80+
[InlineData(true, true, ItemDestination.Remove)]
81+
[InlineData(true, false, ItemDestination.Warm)]
82+
[InlineData(false, false, ItemDestination.Cold)]
83+
public void RouteWarm(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
84+
{
85+
var item = CreateItem(wasAccessed, isExpired);
86+
87+
this.policy.RouteWarm(item).Should().Be(expectedDestination);
88+
}
89+
90+
[Theory]
91+
[InlineData(false, true, ItemDestination.Remove)]
92+
[InlineData(true, true, ItemDestination.Remove)]
93+
[InlineData(true, false, ItemDestination.Warm)]
94+
[InlineData(false, false, ItemDestination.Remove)]
95+
public void RouteCold(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
96+
{
97+
var item = CreateItem(wasAccessed, isExpired);
98+
99+
this.policy.RouteCold(item).Should().Be(expectedDestination);
100+
}
101+
102+
private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpired)
103+
{
104+
var item = this.policy.CreateItem(1, 2);
105+
106+
item.WasAccessed = wasAccessed;
107+
108+
if (isExpired)
109+
{
110+
item.TickCount = Stopwatch.GetTimestamp() - TimeSpan.FromSeconds(11).Ticks;
111+
}
112+
113+
return item;
114+
}
115+
}
116+
}

BitFaster.Caching/Lru/ConcurrentTLru.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
namespace BitFaster.Caching.Lru
88
{
9-
public sealed class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruDateTimePolicy<K, V>, HitCounter>
9+
public sealed class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, LongTickCountLruItem<K, V>, TLruLongTicksPolicy<K, V>, HitCounter>
1010
{
1111
public ConcurrentTLru(int capacity)
12-
: base(Defaults.ConcurrencyLevel, capacity, EqualityComparer<K>.Default, new TLruDateTimePolicy<K, V>(), new HitCounter())
12+
: base(Defaults.ConcurrencyLevel, capacity, EqualityComparer<K>.Default, new TLruLongTicksPolicy<K, V>(), new HitCounter())
1313
{
1414
}
1515

1616
public ConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
17-
: base(concurrencyLevel, capacity, comparer, new TLruDateTimePolicy<K, V>(timeToLive), new HitCounter())
17+
: base(concurrencyLevel, capacity, comparer, new TLruLongTicksPolicy<K, V>(timeToLive), new HitCounter())
1818
{
1919
}
2020

BitFaster.Caching/Lru/FastConcurrentTLru.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44

55
namespace BitFaster.Caching.Lru
66
{
7-
public sealed class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TickCountLruItem<K, V>, TLruTicksPolicy<K, V>, NullHitCounter>
7+
public sealed class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, LongTickCountLruItem<K, V>, TLruLongTicksPolicy<K, V>, NullHitCounter>
88
{
99
public FastConcurrentTLru(int capacity)
10-
: base(Defaults.ConcurrencyLevel, capacity, EqualityComparer<K>.Default, new TLruTicksPolicy<K, V>(), new NullHitCounter())
10+
: base(Defaults.ConcurrencyLevel, capacity, EqualityComparer<K>.Default, new TLruLongTicksPolicy<K, V>(), new NullHitCounter())
1111
{
1212
}
1313

1414
public FastConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
15-
: base(concurrencyLevel, capacity, comparer, new TLruTicksPolicy<K, V>(timeToLive), new NullHitCounter())
15+
: base(concurrencyLevel, capacity, comparer, new TLruLongTicksPolicy<K, V>(timeToLive), new NullHitCounter())
1616
{
1717
}
1818
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace BitFaster.Caching.Lru
8+
{
9+
public class LongTickCountLruItem<K, V> : LruItem<K, V>
10+
{
11+
public LongTickCountLruItem(K key, V value, long tickCount)
12+
: base(key, value)
13+
{
14+
this.TickCount = tickCount;
15+
}
16+
17+
public long TickCount { get; set; }
18+
}
19+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Runtime.CompilerServices;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
namespace BitFaster.Caching.Lru
10+
{
11+
/// <summary>
12+
/// Time aware Least Recently Used (TLRU) is a variant of LRU which discards the least
13+
/// recently used items first, and any item that has expired.
14+
/// </summary>
15+
/// <remarks>
16+
/// This class measures time using stopwatch.
17+
/// </remarks>
18+
public readonly struct TLruLongTicksPolicy<K, V> : IPolicy<K, V, LongTickCountLruItem<K, V>>
19+
{
20+
private readonly long timeToLive;
21+
22+
public TLruLongTicksPolicy(TimeSpan timeToLive)
23+
{
24+
this.timeToLive = timeToLive.Ticks;
25+
}
26+
27+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
28+
public LongTickCountLruItem<K, V> CreateItem(K key, V value)
29+
{
30+
return new LongTickCountLruItem<K, V>(key, value, Stopwatch.GetTimestamp());
31+
}
32+
33+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
34+
public void Touch(LongTickCountLruItem<K, V> item)
35+
{
36+
item.WasAccessed = true;
37+
}
38+
39+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
40+
public bool ShouldDiscard(LongTickCountLruItem<K, V> item)
41+
{
42+
if (Stopwatch.GetTimestamp() - item.TickCount > this.timeToLive)
43+
{
44+
return true;
45+
}
46+
47+
return false;
48+
}
49+
50+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
51+
public ItemDestination RouteHot(LongTickCountLruItem<K, V> item)
52+
{
53+
if (this.ShouldDiscard(item))
54+
{
55+
return ItemDestination.Remove;
56+
}
57+
58+
if (item.WasAccessed)
59+
{
60+
return ItemDestination.Warm;
61+
}
62+
63+
return ItemDestination.Cold;
64+
}
65+
66+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
67+
public ItemDestination RouteWarm(LongTickCountLruItem<K, V> item)
68+
{
69+
if (this.ShouldDiscard(item))
70+
{
71+
return ItemDestination.Remove;
72+
}
73+
74+
if (item.WasAccessed)
75+
{
76+
return ItemDestination.Warm;
77+
}
78+
79+
return ItemDestination.Cold;
80+
}
81+
82+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
83+
public ItemDestination RouteCold(LongTickCountLruItem<K, V> item)
84+
{
85+
if (this.ShouldDiscard(item))
86+
{
87+
return ItemDestination.Remove;
88+
}
89+
90+
if (item.WasAccessed)
91+
{
92+
return ItemDestination.Warm;
93+
}
94+
95+
return ItemDestination.Remove;
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)