Skip to content

Commit df0bb45

Browse files
authored
Use Environment.TickCount64 for TLRU on .NET6 (#331)
* test * use tick64 * fast * comments ---------
1 parent b0c352a commit df0bb45

File tree

9 files changed

+329
-24
lines changed

9 files changed

+329
-24
lines changed

BitFaster.Caching.Benchmarks/Lru/TLruTimeBenchmark.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ private static readonly ConcurrentLruCore<int, int, TickCountLruItem<int, int>,
2222
= new ConcurrentLruCore<int, int, TickCountLruItem<int, int>, TLruTicksPolicy<int, int>, NoTelemetryPolicy<int, int>>
2323
(1, new EqualCapacityPartition(3), EqualityComparer<int>.Default, new TLruTicksPolicy<int, int>(TimeSpan.FromSeconds(1)), default);
2424

25-
private static readonly ConcurrentLruCore<int, int, LongTickCountLruItem<int, int>, TLruLongTicksPolicy<int, int>, NoTelemetryPolicy<int, int>> stopwatchTLru
26-
= new ConcurrentLruCore<int, int, LongTickCountLruItem<int, int>, TLruLongTicksPolicy<int, int>, NoTelemetryPolicy<int, int>>
27-
(1, new EqualCapacityPartition(3), EqualityComparer<int>.Default, new TLruLongTicksPolicy<int, int>(TimeSpan.FromSeconds(1)), default);
25+
private static readonly ConcurrentLruCore<int, int, LongTickCountLruItem<int, int>, TlruStopwatchPolicy<int, int>, NoTelemetryPolicy<int, int>> stopwatchTLru
26+
= new ConcurrentLruCore<int, int, LongTickCountLruItem<int, int>, TlruStopwatchPolicy<int, int>, NoTelemetryPolicy<int, int>>
27+
(1, new EqualCapacityPartition(3), EqualityComparer<int>.Default, new TlruStopwatchPolicy<int, int>(TimeSpan.FromSeconds(1)), default);
2828

2929
[Benchmark(Baseline = true)]
3030
public void DateTimeUtcNow()

BitFaster.Caching.Benchmarks/TimeBenchmarks.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ public int EnvironmentTickCount()
2424
return Environment.TickCount;
2525
}
2626

27+
[Benchmark()]
28+
public long EnvironmentTickCount64()
29+
{
30+
#if NETCOREAPP3_0_OR_GREATER
31+
return Environment.TickCount64;
32+
#else
33+
return 0;
34+
#endif
35+
}
36+
2737
[Benchmark()]
2838
public long StopWatchGetElapsed()
2939
{
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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+
11+
namespace BitFaster.Caching.UnitTests.Lru
12+
{
13+
public class TLruTickCount64PolicyTests
14+
{
15+
private readonly TLruTickCount64Policy<int, int> policy = new TLruTickCount64Policy<int, int>(TimeSpan.FromSeconds(10));
16+
17+
[Fact]
18+
public void TimeToLiveShouldBeTenSecs()
19+
{
20+
this.policy.TimeToLive.Should().Be(TimeSpan.FromSeconds(10));
21+
}
22+
23+
[Fact]
24+
public void CreateItemInitializesKeyAndValue()
25+
{
26+
var item = this.policy.CreateItem(1, 2);
27+
28+
item.Key.Should().Be(1);
29+
item.Value.Should().Be(2);
30+
}
31+
32+
[Fact]
33+
public void CreateItemInitializesTimestampToNow()
34+
{
35+
var item = this.policy.CreateItem(1, 2);
36+
37+
item.TickCount.Should().BeCloseTo(Environment.TickCount64, 20);
38+
}
39+
40+
[Fact]
41+
public void TouchUpdatesItemWasAccessed()
42+
{
43+
var item = this.policy.CreateItem(1, 2);
44+
item.WasAccessed = false;
45+
46+
this.policy.Touch(item);
47+
48+
item.WasAccessed.Should().BeTrue();
49+
}
50+
51+
[Fact]
52+
public async Task UpdateUpdatesTickCount()
53+
{
54+
var item = this.policy.CreateItem(1, 2);
55+
var tc = item.TickCount;
56+
57+
await Task.Delay(TimeSpan.FromMilliseconds(1));
58+
59+
this.policy.Update(item);
60+
61+
item.TickCount.Should().BeGreaterThan(tc);
62+
}
63+
64+
[Fact]
65+
public void WhenItemIsExpiredShouldDiscardIsTrue()
66+
{
67+
var item = this.policy.CreateItem(1, 2);
68+
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(11).ToEnvTick64();
69+
70+
this.policy.ShouldDiscard(item).Should().BeTrue();
71+
}
72+
73+
[Fact]
74+
public void WhenItemIsNotExpiredShouldDiscardIsFalse()
75+
{
76+
var item = this.policy.CreateItem(1, 2);
77+
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(9).ToEnvTick64();
78+
79+
this.policy.ShouldDiscard(item).Should().BeFalse();
80+
}
81+
82+
[Fact]
83+
public void CanDiscardIsTrue()
84+
{
85+
this.policy.CanDiscard().Should().BeTrue();
86+
}
87+
88+
[Theory]
89+
[InlineData(false, true, ItemDestination.Remove)]
90+
[InlineData(true, true, ItemDestination.Remove)]
91+
[InlineData(true, false, ItemDestination.Warm)]
92+
[InlineData(false, false, ItemDestination.Cold)]
93+
public void RouteHot(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
94+
{
95+
var item = CreateItem(wasAccessed, isExpired);
96+
97+
this.policy.RouteHot(item).Should().Be(expectedDestination);
98+
}
99+
100+
[Theory]
101+
[InlineData(false, true, ItemDestination.Remove)]
102+
[InlineData(true, true, ItemDestination.Remove)]
103+
[InlineData(true, false, ItemDestination.Warm)]
104+
[InlineData(false, false, ItemDestination.Cold)]
105+
public void RouteWarm(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
106+
{
107+
var item = CreateItem(wasAccessed, isExpired);
108+
109+
this.policy.RouteWarm(item).Should().Be(expectedDestination);
110+
}
111+
112+
[Theory]
113+
[InlineData(false, true, ItemDestination.Remove)]
114+
[InlineData(true, true, ItemDestination.Remove)]
115+
[InlineData(true, false, ItemDestination.Warm)]
116+
[InlineData(false, false, ItemDestination.Remove)]
117+
public void RouteCold(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
118+
{
119+
var item = CreateItem(wasAccessed, isExpired);
120+
121+
this.policy.RouteCold(item).Should().Be(expectedDestination);
122+
}
123+
124+
private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpired)
125+
{
126+
var item = this.policy.CreateItem(1, 2);
127+
128+
item.WasAccessed = wasAccessed;
129+
130+
if (isExpired)
131+
{
132+
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTick64();
133+
}
134+
135+
return item;
136+
}
137+
}
138+
}

BitFaster.Caching.UnitTests/Lru/TlruLongTicksPolicyTests.cs renamed to BitFaster.Caching.UnitTests/Lru/TlruStopwatchPolicyTests.cs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
using FluentAssertions;
2-
using FluentAssertions.Extensions;
32
using BitFaster.Caching.Lru;
43
using System;
5-
using System.Collections.Generic;
6-
using System.Linq;
7-
using System.Text;
84
using System.Threading.Tasks;
95
using Xunit;
106
using System.Diagnostics;
117

128
namespace BitFaster.Caching.UnitTests.Lru
139
{
14-
public class TLruLongTicksPolicyTests
10+
public class TlruStopwatchPolicyTests
1511
{
16-
private readonly TLruLongTicksPolicy<int, int> policy = new TLruLongTicksPolicy<int, int>(TimeSpan.FromSeconds(10));
12+
private readonly TlruStopwatchPolicy<int, int> policy = new TlruStopwatchPolicy<int, int>(TimeSpan.FromSeconds(10));
1713

1814
[Fact]
1915
public void TimeToLiveShouldBeTenSecs()
@@ -68,7 +64,7 @@ public async Task UpdateUpdatesTickCount()
6864
public void WhenItemIsExpiredShouldDiscardIsTrue()
6965
{
7066
var item = this.policy.CreateItem(1, 2);
71-
item.TickCount = Stopwatch.GetTimestamp() - TLruLongTicksPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(11));
67+
item.TickCount = Stopwatch.GetTimestamp() - TlruStopwatchPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(11));
7268

7369
this.policy.ShouldDiscard(item).Should().BeTrue();
7470
}
@@ -77,7 +73,7 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
7773
public void WhenItemIsNotExpiredShouldDiscardIsFalse()
7874
{
7975
var item = this.policy.CreateItem(1, 2);
80-
item.TickCount = Stopwatch.GetTimestamp() - TLruLongTicksPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(9));
76+
item.TickCount = Stopwatch.GetTimestamp() - TlruStopwatchPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(9));
8177

8278
this.policy.ShouldDiscard(item).Should().BeFalse();
8379
}
@@ -132,7 +128,7 @@ private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpir
132128

133129
if (isExpired)
134130
{
135-
item.TickCount = Stopwatch.GetTimestamp() - TLruLongTicksPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(11));
131+
item.TickCount = Stopwatch.GetTimestamp() - TlruStopwatchPolicy<int, int>.ToTicks(TimeSpan.FromSeconds(11));
136132
}
137133

138134
return item;

BitFaster.Caching.UnitTests/Lru/TlruTicksPolicyTests.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,10 @@ public static int ToEnvTicks(this TimeSpan ts)
142142
{
143143
return (int)ts.TotalMilliseconds;
144144
}
145+
146+
public static long ToEnvTick64(this TimeSpan ts)
147+
{
148+
return (long)ts.TotalMilliseconds;
149+
}
145150
}
146151
}

BitFaster.Caching/Lru/ConcurrentTLru.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ namespace BitFaster.Caching.Lru
77
///<inheritdoc/>
88
[DebuggerTypeProxy(typeof(CacheDebugView<,>))]
99
[DebuggerDisplay("Count = {Count}/{Capacity}")]
10-
public sealed class ConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TLruLongTicksPolicy<K, V>, TelemetryPolicy<K, V>>
10+
#if NETCOREAPP3_0_OR_GREATER
11+
public sealed class ConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TLruTickCount64Policy<K, V>, TelemetryPolicy<K, V>>
12+
#else
13+
public sealed class ConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TlruStopwatchPolicy<K, V>, TelemetryPolicy<K, V>>
14+
#endif
1115
{
1216
/// <summary>
1317
/// Initializes a new instance of the ConcurrentTLru class with the specified capacity and time to live that has the default
@@ -16,8 +20,12 @@ public sealed class ConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCount
1620
/// <param name="capacity">The maximum number of elements that the ConcurrentTLru can contain.</param>
1721
/// <param name="timeToLive">The time to live for cached values.</param>
1822
public ConcurrentTLru(int capacity, TimeSpan timeToLive)
19-
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TLruLongTicksPolicy<K, V>(timeToLive), default)
20-
{
23+
#if NETCOREAPP3_0_OR_GREATER
24+
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TLruTickCount64Policy<K, V>(timeToLive), default)
25+
#else
26+
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TlruStopwatchPolicy<K, V>(timeToLive), default)
27+
#endif
28+
{
2129
}
2230

2331
/// <summary>
@@ -29,7 +37,11 @@ public ConcurrentTLru(int capacity, TimeSpan timeToLive)
2937
/// <param name="comparer">The IEqualityComparer implementation to use when comparing keys.</param>
3038
/// <param name="timeToLive">The time to live for cached values.</param>
3139
public ConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
32-
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TLruLongTicksPolicy<K, V>(timeToLive), default)
40+
#if NETCOREAPP3_0_OR_GREATER
41+
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TLruTickCount64Policy<K, V>(timeToLive), default)
42+
#else
43+
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TlruStopwatchPolicy<K, V>(timeToLive), default)
44+
#endif
3345
{
3446
}
3547

@@ -42,7 +54,11 @@ public ConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> c
4254
/// <param name="comparer">The IEqualityComparer implementation to use when comparing keys.</param>
4355
/// <param name="timeToLive">The time to live for cached values.</param>
4456
public ConcurrentTLru(int concurrencyLevel, ICapacityPartition capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
45-
: base(concurrencyLevel, capacity, comparer, new TLruLongTicksPolicy<K, V>(timeToLive), default)
57+
#if NETCOREAPP3_0_OR_GREATER
58+
: base(concurrencyLevel, capacity, comparer, new TLruTickCount64Policy<K, V>(timeToLive), default)
59+
#else
60+
: base(concurrencyLevel, capacity, comparer, new TlruStopwatchPolicy<K, V>(timeToLive), default)
61+
#endif
4662
{
4763
}
4864
}

BitFaster.Caching/Lru/FastConcurrentTLru.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ namespace BitFaster.Caching.Lru
77
///<inheritdoc/>
88
[DebuggerTypeProxy(typeof(CacheDebugView<,>))]
99
[DebuggerDisplay("Count = {Count}/{Capacity}")]
10-
public sealed class FastConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TLruLongTicksPolicy<K, V>, NoTelemetryPolicy<K, V>>
10+
#if NETCOREAPP3_0_OR_GREATER
11+
public sealed class FastConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TLruTickCount64Policy<K, V>, NoTelemetryPolicy<K, V>>
12+
#else
13+
public sealed class FastConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickCountLruItem<K, V>, TlruStopwatchPolicy<K, V>, NoTelemetryPolicy<K, V>>
14+
#endif
1115
{
1216
/// <summary>
1317
/// Initializes a new instance of the FastConcurrentTLru class with the specified capacity and time to live that has the default
@@ -16,7 +20,11 @@ public sealed class FastConcurrentTLru<K, V> : ConcurrentLruCore<K, V, LongTickC
1620
/// <param name="capacity">The maximum number of elements that the FastConcurrentTLru can contain.</param>
1721
/// <param name="timeToLive">The time to live for cached values.</param>
1822
public FastConcurrentTLru(int capacity, TimeSpan timeToLive)
19-
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TLruLongTicksPolicy<K, V>(timeToLive), default)
23+
#if NETCOREAPP3_0_OR_GREATER
24+
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TLruTickCount64Policy<K, V>(timeToLive), default)
25+
#else
26+
: base(Defaults.ConcurrencyLevel, new FavorWarmPartition(capacity), EqualityComparer<K>.Default, new TlruStopwatchPolicy<K, V>(timeToLive), default)
27+
#endif
2028
{
2129
}
2230

@@ -29,7 +37,11 @@ public FastConcurrentTLru(int capacity, TimeSpan timeToLive)
2937
/// <param name="comparer">The IEqualityComparer implementation to use when comparing keys.</param>
3038
/// <param name="timeToLive">The time to live for cached values.</param>
3139
public FastConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
32-
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TLruLongTicksPolicy<K, V>(timeToLive), default)
40+
#if NETCOREAPP3_0_OR_GREATER
41+
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TLruTickCount64Policy<K, V>(timeToLive), default)
42+
#else
43+
: base(concurrencyLevel, new FavorWarmPartition(capacity), comparer, new TlruStopwatchPolicy<K, V>(timeToLive), default)
44+
#endif
3345
{
3446
}
3547

@@ -42,7 +54,11 @@ public FastConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<
4254
/// <param name="comparer">The IEqualityComparer implementation to use when comparing keys.</param>
4355
/// <param name="timeToLive">The time to live for cached values.</param>
4456
public FastConcurrentTLru(int concurrencyLevel, ICapacityPartition capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
45-
: base(concurrencyLevel, capacity, comparer, new TLruLongTicksPolicy<K, V>(timeToLive), default)
57+
#if NETCOREAPP3_0_OR_GREATER
58+
: base(concurrencyLevel, capacity, comparer, new TLruTickCount64Policy<K, V>(timeToLive), default)
59+
#else
60+
: base(concurrencyLevel, capacity, comparer, new TlruStopwatchPolicy<K, V>(timeToLive), default)
61+
#endif
4662
{
4763
}
4864
}

BitFaster.Caching/Lru/TlruLongTicksPolicy.cs renamed to BitFaster.Caching/Lru/TlruStopwatchPolicy.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ namespace BitFaster.Caching.Lru
99
/// recently used items first, and any item that has expired.
1010
/// </summary>
1111
/// <remarks>
12-
/// This class measures time using stopwatch.
12+
/// This class measures time using Stopwatch.GetTimestamp() with a resolution of ~1us.
1313
/// </remarks>
1414
[DebuggerDisplay("TTL = {TimeToLive,nq})")]
15-
public readonly struct TLruLongTicksPolicy<K, V> : IItemPolicy<K, V, LongTickCountLruItem<K, V>>
15+
public readonly struct TlruStopwatchPolicy<K, V> : IItemPolicy<K, V, LongTickCountLruItem<K, V>>
1616
{
1717
// On some platforms (e.g. MacOS), stopwatch and timespan have different resolution
1818
private static readonly double stopwatchAdjustmentFactor = Stopwatch.Frequency / (double)TimeSpan.TicksPerSecond;
@@ -22,7 +22,7 @@ namespace BitFaster.Caching.Lru
2222
/// Initializes a new instance of the TLruLongTicksPolicy class with the specified time to live.
2323
/// </summary>
2424
/// <param name="timeToLive">The time to live.</param>
25-
public TLruLongTicksPolicy(TimeSpan timeToLive)
25+
public TlruStopwatchPolicy(TimeSpan timeToLive)
2626
{
2727
this.timeToLive = ToTicks(timeToLive);
2828
}

0 commit comments

Comments
 (0)