Skip to content

Commit 4c91b83

Browse files
authored
Use Stopwatch.GetTimestamp on MacOS (#540)
* macos sw * dump params * more info * use duration * use duration * fix test * cleanup * duration basic tests * more duration tests * update bench * bench duration ---------
1 parent aadf0c7 commit 4c91b83

File tree

8 files changed

+213
-58
lines changed

8 files changed

+213
-58
lines changed

BitFaster.Caching.Benchmarks/TimeBenchmarks.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
34
using BenchmarkDotNet.Attributes;
45
using BenchmarkDotNet.Jobs;
56

@@ -14,12 +15,22 @@ public class TimeBenchmarks
1415
{
1516
private static readonly Stopwatch sw = Stopwatch.StartNew();
1617

18+
// .NET 8 onwards has TimeProvider.System
19+
// https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider.system?view=net-8.0
20+
// This is based on either Stopwatch (high perf timestamp) or UtcNow (time zone based on local)
21+
1722
[Benchmark(Baseline = true)]
1823
public DateTime DateTimeUtcNow()
1924
{
2025
return DateTime.UtcNow;
2126
}
2227

28+
[Benchmark()]
29+
public DateTimeOffset DateTimeOffsetUtcNow()
30+
{
31+
return DateTimeOffset.UtcNow;
32+
}
33+
2334
[Benchmark()]
2435
public int EnvironmentTickCount()
2536
{
@@ -36,6 +47,12 @@ public long EnvironmentTickCount64()
3647
#endif
3748
}
3849

50+
[Benchmark()]
51+
public long PInvokeTickCount64()
52+
{
53+
return TickCount64.Current;
54+
}
55+
3956
[Benchmark()]
4057
public long StopWatchGetElapsed()
4158
{
@@ -47,5 +64,19 @@ public long StopWatchGetTimestamp()
4764
{
4865
return Stopwatch.GetTimestamp();
4966
}
67+
68+
[Benchmark()]
69+
public Duration DurationSinceEpoch()
70+
{
71+
return Duration.SinceEpoch();
72+
}
73+
}
74+
75+
public static class TickCount64
76+
{
77+
public static long Current => GetTickCount64();
78+
79+
[DllImport("kernel32")]
80+
private static extern long GetTickCount64();
5081
}
5182
}
Lines changed: 141 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,160 @@
11
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.InteropServices;
4+
using BitFaster.Caching.Lru;
25
using FluentAssertions;
36
using Xunit;
7+
using Xunit.Abstractions;
48

59
namespace BitFaster.Caching.UnitTests
610
{
711
public class DurationTests
812
{
13+
private readonly ITestOutputHelper testOutputHelper;
14+
15+
public DurationTests(ITestOutputHelper testOutputHelper)
16+
{
17+
this.testOutputHelper = testOutputHelper;
18+
}
19+
20+
[Fact]
21+
public void SinceEpoch()
22+
{
23+
#if NETCOREAPP3_0_OR_GREATER
24+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
25+
{
26+
// eps is 1/200 of a second
27+
ulong eps = (ulong)(Stopwatch.Frequency / 200);
28+
Duration.SinceEpoch().raw.Should().BeCloseTo(Stopwatch.GetTimestamp(), eps);
29+
}
30+
else
31+
{
32+
Duration.SinceEpoch().raw.Should().BeCloseTo(Environment.TickCount64, 15);
33+
}
34+
#else
35+
// eps is 1/200 of a second
36+
ulong eps = (ulong)(Stopwatch.Frequency / 200);
37+
Duration.SinceEpoch().raw.Should().BeCloseTo(Stopwatch.GetTimestamp(), eps);
38+
#endif
39+
}
40+
41+
[Fact]
42+
public void ToTimeSpan()
43+
{
44+
#if NETCOREAPP3_0_OR_GREATER
45+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
46+
{
47+
new Duration(100).ToTimeSpan().Should().BeCloseTo(new TimeSpan(100), TimeSpan.FromMilliseconds(50));
48+
}
49+
else
50+
{
51+
new Duration(1000).ToTimeSpan().Should().BeCloseTo(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(10));
52+
}
53+
#else
54+
// for Stopwatch.GetTimestamp() this is number of ticks
55+
new Duration(1 * Stopwatch.Frequency).ToTimeSpan().Should().BeCloseTo(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(10));
56+
#endif
57+
}
58+
59+
[Fact]
60+
public void FromTimeSpan()
61+
{
62+
#if NETCOREAPP3_0_OR_GREATER
63+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
64+
{
65+
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
66+
.Should().Be(Stopwatch.Frequency);
67+
}
68+
else
69+
{
70+
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
71+
.Should().Be((long)TimeSpan.FromSeconds(1).TotalMilliseconds);
72+
}
73+
#else
74+
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
75+
.Should().Be(Stopwatch.Frequency);
76+
#endif
77+
}
78+
79+
[Fact]
80+
public void RoundTripMilliseconds()
81+
{
82+
Duration.FromMilliseconds(2000)
83+
.ToTimeSpan()
84+
.Should().BeCloseTo(TimeSpan.FromMilliseconds(2000), TimeSpan.FromMilliseconds(50));
85+
}
86+
87+
[Fact]
88+
public void RoundTripSeconds()
89+
{
90+
Duration.FromSeconds(2)
91+
.ToTimeSpan()
92+
.Should().BeCloseTo(TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(50));
93+
}
94+
95+
[Fact]
96+
public void RoundTripMinutes()
97+
{
98+
Duration.FromMinutes(2)
99+
.ToTimeSpan()
100+
.Should().BeCloseTo(TimeSpan.FromMinutes(2), TimeSpan.FromMilliseconds(100));
101+
}
102+
9103
[Fact]
10104
public void RoundTripHours()
11105
{
12-
var d = Duration.FromHours(2);
13-
d.ToTimeSpan().Should().BeCloseTo(TimeSpan.FromHours(2), TimeSpan.FromMilliseconds(100));
106+
Duration.FromHours(2)
107+
.ToTimeSpan()
108+
.Should().BeCloseTo(TimeSpan.FromHours(2), TimeSpan.FromMilliseconds(100));
14109
}
15110

16111
[Fact]
17112
public void RoundTripDays()
18113
{
19-
var d = Duration.FromDays(2);
20-
d.ToTimeSpan().Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
114+
Duration.FromDays(2)
115+
.ToTimeSpan()
116+
.Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
117+
}
118+
119+
[Fact]
120+
public void OperatorPlus()
121+
{
122+
(Duration.FromDays(2) + Duration.FromDays(2))
123+
.ToTimeSpan()
124+
.Should().BeCloseTo(TimeSpan.FromDays(4), TimeSpan.FromMilliseconds(100));
125+
}
126+
127+
[Fact]
128+
public void OperatorMinus()
129+
{
130+
(Duration.FromDays(4) - Duration.FromDays(2))
131+
.ToTimeSpan()
132+
.Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
133+
}
134+
135+
[Fact]
136+
public void OperatorGreater()
137+
{
138+
(Duration.FromDays(4) > Duration.FromDays(2))
139+
.Should().BeTrue();
140+
}
141+
142+
[Fact]
143+
public void OperatorLess()
144+
{
145+
(Duration.FromDays(4) < Duration.FromDays(2))
146+
.Should().BeFalse();
147+
}
148+
149+
// This is for diagnostic purposes when tests run on different operating systems.
150+
[Fact]
151+
public void OutputTimeParameters()
152+
{
153+
this.testOutputHelper.WriteLine($"Stopwatch.Frequency {Stopwatch.Frequency}");
154+
this.testOutputHelper.WriteLine($"TimeSpan.TicksPerSecond {TimeSpan.TicksPerSecond}");
155+
this.testOutputHelper.WriteLine($"stopwatchAdjustmentFactor {StopwatchTickConverter.stopwatchAdjustmentFactor}");
156+
var d = Duration.SinceEpoch();
157+
this.testOutputHelper.WriteLine($"Duration.SinceEpoch {d.raw} ({d.ToTimeSpan()})");
21158
}
22159
}
23160
}

BitFaster.Caching.UnitTests/Lfu/TimerWheelTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public void WhenAdvanceThrowsCurrentTimeIsNotAdvanced()
155155
timerWheel.Schedule(AddNode(1, new DisposeThrows(), new Duration(clock.raw + TimerWheel.Spans[1])));
156156

157157
// This should expire the node, call evict, then throw via DisposeThrows.Dispose()
158-
Action advance = () => timerWheel.Advance(ref cache, new Duration(clock.raw + int.MaxValue));
158+
Action advance = () => timerWheel.Advance(ref cache, new Duration(clock.raw + (2 * TimerWheel.Spans[1])));
159159
advance.Should().Throw<InvalidOperationException>();
160160

161161
timerWheel.time.Should().Be(clock.raw);

BitFaster.Caching.UnitTests/Lru/AfterAccessPolicyTests.cs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
using System.Threading.Tasks;
55
using Xunit;
66

7-
#if NETFRAMEWORK
8-
using System.Diagnostics;
9-
#endif
10-
117
namespace BitFaster.Caching.UnitTests.Lru
128
{
139
public class AfterAccessPolicyTests
@@ -57,14 +53,7 @@ public void CreateItemInitializesTimestampToNow()
5753
{
5854
var item = this.policy.CreateItem(1, 2);
5955

60-
#if NETFRAMEWORK
61-
var expected = Stopwatch.GetTimestamp();
62-
ulong epsilon = (ulong)(TimeSpan.FromMilliseconds(20).TotalSeconds * Stopwatch.Frequency);
63-
#else
64-
var expected = Environment.TickCount64;
65-
ulong epsilon = 20;
66-
#endif
67-
item.TickCount.Should().BeCloseTo(expected, epsilon);
56+
item.TickCount.Should().BeCloseTo(Duration.SinceEpoch().raw, Duration.epsilon);
6857
}
6958

7059
[Fact]
@@ -109,11 +98,7 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
10998
{
11099
var item = this.policy.CreateItem(1, 2);
111100

112-
#if NETFRAMEWORK
113-
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(11));
114-
#else
115-
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(11).ToEnvTick64();
116-
#endif
101+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
117102

118103
this.policy.ShouldDiscard(item).Should().BeTrue();
119104
}
@@ -123,11 +108,7 @@ public void WhenItemIsNotExpiredShouldDiscardIsFalse()
123108
{
124109
var item = this.policy.CreateItem(1, 2);
125110

126-
#if NETFRAMEWORK
127-
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(9));
128-
#else
129-
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(9).ToEnvTick64();
130-
#endif
111+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(9).raw;
131112

132113
this.policy.ShouldDiscard(item).Should().BeFalse();
133114
}
@@ -182,11 +163,7 @@ private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpir
182163

183164
if (isExpired)
184165
{
185-
#if NETFRAMEWORK
186-
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(11));
187-
#else
188-
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTick64();
189-
#endif
166+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
190167
}
191168

192169
return item;

BitFaster.Caching.UnitTests/Lru/DiscretePolicyTests.cs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ public class DiscretePolicyTests
1111
private readonly TestExpiryCalculator<int, int> expiryCalculator;
1212
private readonly DiscretePolicy<int, int> policy;
1313

14-
private static readonly ulong epsilon = (ulong)Duration.FromMilliseconds(20).raw;
15-
1614
public DiscretePolicyTests()
1715
{
1816
expiryCalculator = new TestExpiryCalculator<int, int>();
@@ -50,7 +48,7 @@ public void CreateItemInitializesKeyValueAndTicks()
5048
item.Key.Should().Be(1);
5149
item.Value.Should().Be(2);
5250

53-
item.TickCount.Should().BeCloseTo(timeToExpire.raw + Duration.SinceEpoch().raw, epsilon);
51+
item.TickCount.Should().BeCloseTo(timeToExpire.raw + Duration.SinceEpoch().raw, Duration.epsilon);
5452
}
5553

5654
[Fact]
@@ -95,11 +93,8 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
9593
{
9694
var item = this.policy.CreateItem(1, 2);
9795

98-
#if NETFRAMEWORK
99-
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(11));
100-
#else
101-
item.TickCount = item.TickCount - TimeSpan.FromMilliseconds(11).ToEnvTick64();
102-
#endif
96+
item.TickCount = item.TickCount - Duration.FromMilliseconds(11).raw;
97+
10398
this.policy.ShouldDiscard(item).Should().BeTrue();
10499
}
105100

@@ -108,11 +103,7 @@ public void WhenItemIsNotExpiredShouldDiscardIsFalse()
108103
{
109104
var item = this.policy.CreateItem(1, 2);
110105

111-
#if NETFRAMEWORK
112-
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(9));
113-
#else
114-
item.TickCount = item.TickCount - (int)TimeSpan.FromMilliseconds(9).ToEnvTick64();
115-
#endif
106+
item.TickCount = item.TickCount - Duration.FromMilliseconds(9).raw;
116107

117108
this.policy.ShouldDiscard(item).Should().BeFalse();
118109
}
@@ -168,11 +159,7 @@ private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpir
168159

169160
if (isExpired)
170161
{
171-
#if NETFRAMEWORK
172-
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(11));
173-
#else
174-
item.TickCount = item.TickCount - TimeSpan.FromMilliseconds(11).ToEnvTick64();
175-
#endif
162+
item.TickCount = item.TickCount - Duration.FromMilliseconds(11).raw;
176163
}
177164

178165
return item;

BitFaster.Caching.UnitTests/Lru/TLruTickCount64PolicyTests .cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void CreateItemInitializesTimestampToNow()
5656
{
5757
var item = this.policy.CreateItem(1, 2);
5858

59-
item.TickCount.Should().BeCloseTo(Environment.TickCount64, 20);
59+
item.TickCount.Should().BeCloseTo(Duration.SinceEpoch().raw, Duration.epsilon);
6060
}
6161

6262
[Fact]
@@ -87,7 +87,7 @@ public async Task UpdateUpdatesTickCount()
8787
public void WhenItemIsExpiredShouldDiscardIsTrue()
8888
{
8989
var item = this.policy.CreateItem(1, 2);
90-
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(11).ToEnvTick64();
90+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
9191

9292
this.policy.ShouldDiscard(item).Should().BeTrue();
9393
}
@@ -96,7 +96,7 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
9696
public void WhenItemIsNotExpiredShouldDiscardIsFalse()
9797
{
9898
var item = this.policy.CreateItem(1, 2);
99-
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(9).ToEnvTick64();
99+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(9).raw;
100100

101101
this.policy.ShouldDiscard(item).Should().BeFalse();
102102
}
@@ -151,7 +151,7 @@ private LongTickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpir
151151

152152
if (isExpired)
153153
{
154-
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTick64();
154+
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
155155
}
156156

157157
return item;

0 commit comments

Comments
 (0)