Skip to content

Use Stopwatch.GetTimestamp on MacOS #540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions BitFaster.Caching.Benchmarks/TimeBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;

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

// .NET 8 onwards has TimeProvider.System
// https://learn.microsoft.com/en-us/dotnet/api/system.timeprovider.system?view=net-8.0
// This is based on either Stopwatch (high perf timestamp) or UtcNow (time zone based on local)

[Benchmark(Baseline = true)]
public DateTime DateTimeUtcNow()
{
return DateTime.UtcNow;
}

[Benchmark()]
public DateTimeOffset DateTimeOffsetUtcNow()
{
return DateTimeOffset.UtcNow;
}

[Benchmark()]
public int EnvironmentTickCount()
{
Expand All @@ -36,6 +47,12 @@ public long EnvironmentTickCount64()
#endif
}

[Benchmark()]
public long PInvokeTickCount64()
{
return TickCount64.Current;
}

[Benchmark()]
public long StopWatchGetElapsed()
{
Expand All @@ -47,5 +64,19 @@ public long StopWatchGetTimestamp()
{
return Stopwatch.GetTimestamp();
}

[Benchmark()]
public Duration DurationSinceEpoch()
{
return Duration.SinceEpoch();
}
}

public static class TickCount64
{
public static long Current => GetTickCount64();

[DllImport("kernel32")]
private static extern long GetTickCount64();
}
}
145 changes: 141 additions & 4 deletions BitFaster.Caching.UnitTests/DurationTests.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,160 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using BitFaster.Caching.Lru;
using FluentAssertions;
using Xunit;
using Xunit.Abstractions;

namespace BitFaster.Caching.UnitTests
{
public class DurationTests
{
private readonly ITestOutputHelper testOutputHelper;

public DurationTests(ITestOutputHelper testOutputHelper)
{
this.testOutputHelper = testOutputHelper;
}

[Fact]
public void SinceEpoch()
{
#if NETCOREAPP3_0_OR_GREATER
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// eps is 1/200 of a second
ulong eps = (ulong)(Stopwatch.Frequency / 200);
Duration.SinceEpoch().raw.Should().BeCloseTo(Stopwatch.GetTimestamp(), eps);
}
else
{
Duration.SinceEpoch().raw.Should().BeCloseTo(Environment.TickCount64, 15);
}
#else
// eps is 1/200 of a second
ulong eps = (ulong)(Stopwatch.Frequency / 200);
Duration.SinceEpoch().raw.Should().BeCloseTo(Stopwatch.GetTimestamp(), eps);
#endif
}

[Fact]
public void ToTimeSpan()
{
#if NETCOREAPP3_0_OR_GREATER
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
new Duration(100).ToTimeSpan().Should().BeCloseTo(new TimeSpan(100), TimeSpan.FromMilliseconds(50));
}
else
{
new Duration(1000).ToTimeSpan().Should().BeCloseTo(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(10));
}
#else
// for Stopwatch.GetTimestamp() this is number of ticks
new Duration(1 * Stopwatch.Frequency).ToTimeSpan().Should().BeCloseTo(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(10));
#endif
}

[Fact]
public void FromTimeSpan()
{
#if NETCOREAPP3_0_OR_GREATER
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
.Should().Be(Stopwatch.Frequency);
}
else
{
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
.Should().Be((long)TimeSpan.FromSeconds(1).TotalMilliseconds);
}
#else
Duration.FromTimeSpan(TimeSpan.FromSeconds(1)).raw
.Should().Be(Stopwatch.Frequency);
#endif
}

[Fact]
public void RoundTripMilliseconds()
{
Duration.FromMilliseconds(2000)
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromMilliseconds(2000), TimeSpan.FromMilliseconds(50));
}

[Fact]
public void RoundTripSeconds()
{
Duration.FromSeconds(2)
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromSeconds(2), TimeSpan.FromMilliseconds(50));
}

[Fact]
public void RoundTripMinutes()
{
Duration.FromMinutes(2)
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromMinutes(2), TimeSpan.FromMilliseconds(100));
}

[Fact]
public void RoundTripHours()
{
var d = Duration.FromHours(2);
d.ToTimeSpan().Should().BeCloseTo(TimeSpan.FromHours(2), TimeSpan.FromMilliseconds(100));
Duration.FromHours(2)
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromHours(2), TimeSpan.FromMilliseconds(100));
}

[Fact]
public void RoundTripDays()
{
var d = Duration.FromDays(2);
d.ToTimeSpan().Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
Duration.FromDays(2)
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
}

[Fact]
public void OperatorPlus()
{
(Duration.FromDays(2) + Duration.FromDays(2))
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromDays(4), TimeSpan.FromMilliseconds(100));
}

[Fact]
public void OperatorMinus()
{
(Duration.FromDays(4) - Duration.FromDays(2))
.ToTimeSpan()
.Should().BeCloseTo(TimeSpan.FromDays(2), TimeSpan.FromMilliseconds(100));
}

[Fact]
public void OperatorGreater()
{
(Duration.FromDays(4) > Duration.FromDays(2))
.Should().BeTrue();
}

[Fact]
public void OperatorLess()
{
(Duration.FromDays(4) < Duration.FromDays(2))
.Should().BeFalse();
}

// This is for diagnostic purposes when tests run on different operating systems.
[Fact]
public void OutputTimeParameters()
{
this.testOutputHelper.WriteLine($"Stopwatch.Frequency {Stopwatch.Frequency}");
this.testOutputHelper.WriteLine($"TimeSpan.TicksPerSecond {TimeSpan.TicksPerSecond}");
this.testOutputHelper.WriteLine($"stopwatchAdjustmentFactor {StopwatchTickConverter.stopwatchAdjustmentFactor}");
var d = Duration.SinceEpoch();
this.testOutputHelper.WriteLine($"Duration.SinceEpoch {d.raw} ({d.ToTimeSpan()})");
}
}
}
2 changes: 1 addition & 1 deletion BitFaster.Caching.UnitTests/Lfu/TimerWheelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public void WhenAdvanceThrowsCurrentTimeIsNotAdvanced()
timerWheel.Schedule(AddNode(1, new DisposeThrows(), new Duration(clock.raw + TimerWheel.Spans[1])));

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

timerWheel.time.Should().Be(clock.raw);
Expand Down
31 changes: 4 additions & 27 deletions BitFaster.Caching.UnitTests/Lru/AfterAccessPolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
using System.Threading.Tasks;
using Xunit;

#if NETFRAMEWORK
using System.Diagnostics;
#endif

namespace BitFaster.Caching.UnitTests.Lru
{
public class AfterAccessPolicyTests
Expand Down Expand Up @@ -57,14 +53,7 @@ public void CreateItemInitializesTimestampToNow()
{
var item = this.policy.CreateItem(1, 2);

#if NETFRAMEWORK
var expected = Stopwatch.GetTimestamp();
ulong epsilon = (ulong)(TimeSpan.FromMilliseconds(20).TotalSeconds * Stopwatch.Frequency);
#else
var expected = Environment.TickCount64;
ulong epsilon = 20;
#endif
item.TickCount.Should().BeCloseTo(expected, epsilon);
item.TickCount.Should().BeCloseTo(Duration.SinceEpoch().raw, Duration.epsilon);
}

[Fact]
Expand Down Expand Up @@ -109,11 +98,7 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
{
var item = this.policy.CreateItem(1, 2);

#if NETFRAMEWORK
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(11));
#else
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(11).ToEnvTick64();
#endif
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;

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

#if NETFRAMEWORK
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(9));
#else
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(9).ToEnvTick64();
#endif
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(9).raw;

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

if (isExpired)
{
#if NETFRAMEWORK
item.TickCount = Stopwatch.GetTimestamp() - StopwatchTickConverter.ToTicks(TimeSpan.FromSeconds(11));
#else
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTick64();
#endif
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
}

return item;
Expand Down
23 changes: 5 additions & 18 deletions BitFaster.Caching.UnitTests/Lru/DiscretePolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public class DiscretePolicyTests
private readonly TestExpiryCalculator<int, int> expiryCalculator;
private readonly DiscretePolicy<int, int> policy;

private static readonly ulong epsilon = (ulong)Duration.FromMilliseconds(20).raw;

public DiscretePolicyTests()
{
expiryCalculator = new TestExpiryCalculator<int, int>();
Expand Down Expand Up @@ -50,7 +48,7 @@ public void CreateItemInitializesKeyValueAndTicks()
item.Key.Should().Be(1);
item.Value.Should().Be(2);

item.TickCount.Should().BeCloseTo(timeToExpire.raw + Duration.SinceEpoch().raw, epsilon);
item.TickCount.Should().BeCloseTo(timeToExpire.raw + Duration.SinceEpoch().raw, Duration.epsilon);
}

[Fact]
Expand Down Expand Up @@ -95,11 +93,8 @@ public void WhenItemIsExpiredShouldDiscardIsTrue()
{
var item = this.policy.CreateItem(1, 2);

#if NETFRAMEWORK
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(11));
#else
item.TickCount = item.TickCount - TimeSpan.FromMilliseconds(11).ToEnvTick64();
#endif
item.TickCount = item.TickCount - Duration.FromMilliseconds(11).raw;

this.policy.ShouldDiscard(item).Should().BeTrue();
}

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

#if NETFRAMEWORK
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(9));
#else
item.TickCount = item.TickCount - (int)TimeSpan.FromMilliseconds(9).ToEnvTick64();
#endif
item.TickCount = item.TickCount - Duration.FromMilliseconds(9).raw;

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

if (isExpired)
{
#if NETFRAMEWORK
item.TickCount = item.TickCount - StopwatchTickConverter.ToTicks(TimeSpan.FromMilliseconds(11));
#else
item.TickCount = item.TickCount - TimeSpan.FromMilliseconds(11).ToEnvTick64();
#endif
item.TickCount = item.TickCount - Duration.FromMilliseconds(11).raw;
}

return item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void CreateItemInitializesTimestampToNow()
{
var item = this.policy.CreateItem(1, 2);

item.TickCount.Should().BeCloseTo(Environment.TickCount64, 20);
item.TickCount.Should().BeCloseTo(Duration.SinceEpoch().raw, Duration.epsilon);
}

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

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

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

if (isExpired)
{
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTick64();
item.TickCount = Duration.SinceEpoch().raw - Duration.FromSeconds(11).raw;
}

return item;
Expand Down
Loading
Loading