Skip to content

Commit fc04a72

Browse files
authored
measure time with Environment.TickCount (#30)
* use tick count * fix name
1 parent 37749c2 commit fc04a72

File tree

10 files changed

+256
-9
lines changed

10 files changed

+256
-9
lines changed

BitFaster.Caching.Benchmarks/BitFaster.Caching.Benchmarks.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
<NoWarn>1701;1702;CS8002</NoWarn>
1010
</PropertyGroup>
1111

12+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
13+
<NoWarn>1701;1702,CS8002</NoWarn>
14+
</PropertyGroup>
15+
1216
<ItemGroup>
1317
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
1418
<PackageReference Include="MathNet.Numerics" Version="4.11.0" />

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<ZipDistribution>(ManualConfig.Create(DefaultConfig.Instance)
18+
.Run<LruCycle>(ManualConfig.Create(DefaultConfig.Instance)
1919
.AddJob(Job.RyuJitX64));
2020
}
2121
}

BitFaster.Caching.HitRateAnalysis/BitFaster.Caching.HitRateAnalysis.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
<NoWarn>1701;1702;CS8002</NoWarn>
1010
</PropertyGroup>
1111

12+
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
13+
<NoWarn>1701;1702,CS8002</NoWarn>
14+
</PropertyGroup>
15+
1216
<ItemGroup>
1317
<PackageReference Include="ConsoleTables" Version="2.4.1" />
1418
<PackageReference Include="CsvHelper" Version="15.0.5" />

BitFaster.Caching.UnitTests/Lru/TlruPolicyTests.cs renamed to BitFaster.Caching.UnitTests/Lru/TlruDateTimePolicyTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
namespace BitFaster.Caching.UnitTests.Lru
1212
{
13-
public class TLruPolicyTests
13+
public class TLruDateTimePolicyTests
1414
{
15-
private readonly TLruPolicy<int, int> policy = new TLruPolicy<int, int>(TimeSpan.FromSeconds(10));
15+
private readonly TLruDateTimePolicy<int, int> policy = new TLruDateTimePolicy<int, int>(TimeSpan.FromSeconds(10));
1616

1717
[Fact]
1818
public void CreateItemInitializesKeyAndValue()
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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 TLruTicksPolicyTests
14+
{
15+
private readonly TLruTicksPolicy<int, int> policy = new TLruTicksPolicy<int, int>(TimeSpan.FromSeconds(10));
16+
17+
[Fact]
18+
public void CreateItemInitializesKeyAndValue()
19+
{
20+
var item = this.policy.CreateItem(1, 2);
21+
22+
item.Key.Should().Be(1);
23+
item.Value.Should().Be(2);
24+
}
25+
26+
[Fact]
27+
public void CreateItemInitializesTimestampToNow()
28+
{
29+
var item = this.policy.CreateItem(1, 2);
30+
31+
item.TickCount.Should().BeCloseTo(Environment.TickCount, 20);
32+
}
33+
34+
[Fact]
35+
public void TouchUpdatesItemWasAccessed()
36+
{
37+
var item = this.policy.CreateItem(1, 2);
38+
item.WasAccessed = false;
39+
40+
this.policy.Touch(item);
41+
42+
item.WasAccessed.Should().BeTrue();
43+
}
44+
45+
[Fact]
46+
public void WhenItemIsExpiredShouldDiscardIsTrue()
47+
{
48+
var item = this.policy.CreateItem(1, 2);
49+
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(11).ToEnvTicks();
50+
51+
this.policy.ShouldDiscard(item).Should().BeTrue();
52+
}
53+
54+
[Fact]
55+
public void WhenItemIsNotExpiredShouldDiscardIsFalse()
56+
{
57+
var item = this.policy.CreateItem(1, 2);
58+
item.TickCount = Environment.TickCount - (int)TimeSpan.FromSeconds(9).ToEnvTicks();
59+
60+
this.policy.ShouldDiscard(item).Should().BeFalse();
61+
}
62+
63+
[Theory]
64+
[InlineData(false, true, ItemDestination.Remove)]
65+
[InlineData(true, true, ItemDestination.Remove)]
66+
[InlineData(true, false, ItemDestination.Warm)]
67+
[InlineData(false, false, ItemDestination.Cold)]
68+
public void RouteHot(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
69+
{
70+
var item = CreateItem(wasAccessed, isExpired);
71+
72+
this.policy.RouteHot(item).Should().Be(expectedDestination);
73+
}
74+
75+
[Theory]
76+
[InlineData(false, true, ItemDestination.Remove)]
77+
[InlineData(true, true, ItemDestination.Remove)]
78+
[InlineData(true, false, ItemDestination.Warm)]
79+
[InlineData(false, false, ItemDestination.Cold)]
80+
public void RouteWarm(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
81+
{
82+
var item = CreateItem(wasAccessed, isExpired);
83+
84+
this.policy.RouteWarm(item).Should().Be(expectedDestination);
85+
}
86+
87+
[Theory]
88+
[InlineData(false, true, ItemDestination.Remove)]
89+
[InlineData(true, true, ItemDestination.Remove)]
90+
[InlineData(true, false, ItemDestination.Warm)]
91+
[InlineData(false, false, ItemDestination.Remove)]
92+
public void RouteCold(bool wasAccessed, bool isExpired, ItemDestination expectedDestination)
93+
{
94+
var item = CreateItem(wasAccessed, isExpired);
95+
96+
this.policy.RouteCold(item).Should().Be(expectedDestination);
97+
}
98+
99+
private TickCountLruItem<int, int> CreateItem(bool wasAccessed, bool isExpired)
100+
{
101+
var item = this.policy.CreateItem(1, 2);
102+
103+
item.WasAccessed = wasAccessed;
104+
105+
if (isExpired)
106+
{
107+
item.TickCount = Environment.TickCount - TimeSpan.FromSeconds(11).ToEnvTicks();
108+
}
109+
110+
return item;
111+
}
112+
}
113+
114+
public static class TimeSpanExtensions
115+
{
116+
public static int ToEnvTicks(this TimeSpan ts)
117+
{
118+
return (int)ts.TotalMilliseconds;
119+
}
120+
}
121+
}

BitFaster.Caching/Lru/ConcurrentTLru.cs

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

77
namespace BitFaster.Caching.Lru
88
{
9-
public sealed class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, HitCounter>
9+
public sealed class ConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruDateTimePolicy<K, V>, HitCounter>
1010
{
1111
public ConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
12-
: base(concurrencyLevel, capacity, comparer, new TLruPolicy<K, V>(timeToLive), new HitCounter())
12+
: base(concurrencyLevel, capacity, comparer, new TLruDateTimePolicy<K, V>(timeToLive), new HitCounter())
1313
{
1414
}
1515

BitFaster.Caching/Lru/FastConcurrentTLru.cs

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

55
namespace BitFaster.Caching.Lru
66
{
7-
public sealed class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TimeStampedLruItem<K, V>, TLruPolicy<K, V>, NullHitCounter>
7+
public sealed class FastConcurrentTLru<K, V> : TemplateConcurrentLru<K, V, TickCountLruItem<K, V>, TLruTicksPolicy<K, V>, NullHitCounter>
88
{
99
public FastConcurrentTLru(int concurrencyLevel, int capacity, IEqualityComparer<K> comparer, TimeSpan timeToLive)
10-
: base(concurrencyLevel, capacity, comparer, new TLruPolicy<K, V>(timeToLive), new NullHitCounter())
10+
: base(concurrencyLevel, capacity, comparer, new TLruTicksPolicy<K, V>(timeToLive), new NullHitCounter())
1111
{
1212
}
1313
}
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 TickCountLruItem<K, V> : LruItem<K, V>
10+
{
11+
public TickCountLruItem(K key, V value)
12+
: base(key, value)
13+
{
14+
this.TickCount = Environment.TickCount;
15+
}
16+
17+
public int TickCount { get; set; }
18+
}
19+
}

BitFaster.Caching/Lru/TlruPolicy.cs renamed to BitFaster.Caching/Lru/TlruDateTimePolicy.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ namespace BitFaster.Caching.Lru
1111
/// Time aware Least Recently Used (TLRU) is a variant of LRU which discards the least
1212
/// recently used items first, and any item that has expired.
1313
/// </summary>
14-
public readonly struct TLruPolicy<K, V> : IPolicy<K, V, TimeStampedLruItem<K, V>>
14+
public readonly struct TLruDateTimePolicy<K, V> : IPolicy<K, V, TimeStampedLruItem<K, V>>
1515
{
1616
private readonly TimeSpan timeToLive;
1717

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

0 commit comments

Comments
 (0)