Skip to content

Commit 7a5bf5b

Browse files
authored
atomic supports value factory arg (#349)
* atomic * alternative * struct factory * tests * coverage * docs * bench * readonly * big arg * rename ---------
1 parent 60cfc64 commit 7a5bf5b

19 files changed

+562
-37
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+

2+
using System;
3+
using BenchmarkDotNet.Attributes;
4+
using BenchmarkDotNet.Jobs;
5+
6+
namespace BitFaster.Caching.Benchmarks
7+
{
8+
[SimpleJob(RuntimeMoniker.Net48)]
9+
[SimpleJob(RuntimeMoniker.Net60)]
10+
[DisassemblyDiagnoser(printSource: true, maxDepth: 3)]
11+
[MemoryDiagnoser(displayGenColumns: false)]
12+
[HideColumns("Job", "Median", "RatioSD", "Alloc Ratio")]
13+
public class ValueFactoryBenchmarks
14+
{
15+
[Benchmark(Baseline = true)]
16+
public int Delegate()
17+
{
18+
Func<int, int> valueFactory = (k) => k;
19+
return valueFactory(1);
20+
}
21+
22+
[Benchmark()]
23+
public int ValueFactory()
24+
{
25+
var valueFactory = new ValueFactory<int, int>(i => i);
26+
return Invoke<int, int, ValueFactory<int, int>>(valueFactory, 1);
27+
}
28+
29+
[Benchmark()]
30+
public int ValueFactoryRef()
31+
{
32+
var valueFactory = new ValueFactory<int, int>(i => i);
33+
return InvokeRef<int, int, ValueFactory<int, int>>(ref valueFactory, 1);
34+
}
35+
36+
private V Invoke<K, V, TFactory>(TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
37+
{
38+
return factory.Create(key);
39+
}
40+
41+
private V InvokeRef<K, V, TFactory>(ref TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
42+
{
43+
return factory.Create(key);
44+
}
45+
}
46+
47+
48+
[SimpleJob(RuntimeMoniker.Net48)]
49+
[SimpleJob(RuntimeMoniker.Net60)]
50+
[DisassemblyDiagnoser(printSource: true, maxDepth: 3)]
51+
[MemoryDiagnoser(displayGenColumns: false)]
52+
[HideColumns("Job", "Median", "RatioSD", "Alloc Ratio")]
53+
public class ValueFactoryArgBenchmarks
54+
{
55+
[Benchmark(Baseline = true)]
56+
public int Delegate()
57+
{
58+
Func<int, int, int> valueFactory = (k, v) => k + v;
59+
return valueFactory(1, 2);
60+
}
61+
62+
[Benchmark()]
63+
public int ValueFactory()
64+
{
65+
var valueFactory = new ValueFactoryArg<int,int, int>((k, v) => k + v, 2);
66+
return Invoke<int, int, ValueFactoryArg<int, int, int>>(valueFactory, 1);
67+
}
68+
69+
[Benchmark()]
70+
public int ValueFactoryRef()
71+
{
72+
var valueFactory = new ValueFactoryArg<int, int, int>((k, v) => k + v, 2);
73+
return InvokeRef<int, int, ValueFactoryArg<int, int, int>>(ref valueFactory, 1);
74+
}
75+
76+
private V Invoke<K, V, TFactory>(TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
77+
{
78+
return factory.Create(key);
79+
}
80+
81+
private V InvokeRef<K, V, TFactory>(ref TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
82+
{
83+
return factory.Create(key);
84+
}
85+
}
86+
87+
[SimpleJob(RuntimeMoniker.Net48)]
88+
[SimpleJob(RuntimeMoniker.Net60)]
89+
[DisassemblyDiagnoser(printSource: true, maxDepth: 3)]
90+
[MemoryDiagnoser(displayGenColumns: false)]
91+
[HideColumns("Job", "Median", "RatioSD", "Alloc Ratio")]
92+
public class ValueFactoryBigArgBenchmarks
93+
{
94+
[Benchmark(Baseline = true)]
95+
public int Delegate()
96+
{
97+
Func<int, ValueTuple<long, long, long>, int> valueFactory = (k, v) => k;
98+
return valueFactory(1, (0, 1, 2));
99+
}
100+
101+
[Benchmark()]
102+
public int ValueFactory()
103+
{
104+
var valueFactory = new ValueFactoryArg<int, ValueTuple<long, long, long>, int>((k, v) => k , (0, 1, 2));
105+
return Invoke<int, int, ValueFactoryArg<int, ValueTuple<long, long, long>, int>>(valueFactory, 1);
106+
}
107+
108+
[Benchmark()]
109+
public int ValueFactoryRef()
110+
{
111+
var valueFactory = new ValueFactoryArg<int, ValueTuple<long, long, long>, int>((k, v) => k, (0, 1, 2));
112+
return InvokeRef<int, int, ValueFactoryArg<int, ValueTuple<long, long, long>, int>>(ref valueFactory, 1);
113+
}
114+
115+
private V Invoke<K, V, TFactory>(TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
116+
{
117+
return factory.Create(key);
118+
}
119+
120+
private V InvokeRef<K, V, TFactory>(ref TFactory factory, K key) where TFactory : struct, IValueFactory<K, V>
121+
{
122+
return factory.Create(key);
123+
}
124+
}
125+
}

BitFaster.Caching.UnitTests/Atomic/AsyncAtomicFactoryTests.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
52
using System.Threading;
63
using System.Threading.Tasks;
74
using BitFaster.Caching.Atomic;
@@ -40,6 +37,16 @@ public async Task WhenValueCreatedValueReturned()
4037
a.IsValueCreated.Should().BeTrue();
4138
}
4239

40+
[Fact]
41+
public async Task WhenValueCreatedWithArgValueReturned()
42+
{
43+
var a = new AsyncAtomicFactory<int, int>();
44+
(await a.GetValueAsync(1, (k, a) => Task.FromResult(k + a), 7)).Should().Be(8);
45+
46+
a.ValueIfCreated.Should().Be(8);
47+
a.IsValueCreated.Should().BeTrue();
48+
}
49+
4350
[Fact]
4451
public async Task WhenValueCreatedGetValueReturnsOriginalValue()
4552
{
@@ -48,6 +55,14 @@ public async Task WhenValueCreatedGetValueReturnsOriginalValue()
4855
(await a.GetValueAsync(1, k => Task.FromResult(3))).Should().Be(2);
4956
}
5057

58+
[Fact]
59+
public async Task WhenValueCreatedArgGetValueReturnsOriginalValue()
60+
{
61+
var a = new AsyncAtomicFactory<int, int>();
62+
await a.GetValueAsync(1, (k, a) => Task.FromResult(k + a), 7);
63+
(await a.GetValueAsync(1, (k, a) => Task.FromResult(k + a), 9)).Should().Be(8);
64+
}
65+
5166
[Fact]
5267
public async Task WhenValueCreateThrowsValueIsNotStored()
5368
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryAsyncCacheTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ public async Task WhenItemIsAddedThenLookedUpMetricsAreCorrect()
5454
this.cache.Metrics.Value.Hits.Should().Be(1);
5555
}
5656

57+
[Fact]
58+
public async Task WhenItemIsAddedWithArgValueIsCorrect()
59+
{
60+
await this.cache.GetOrAddAsync(1, (k, a) => Task.FromResult(k + a), 2);
61+
62+
this.cache.TryGet(1, out var value).Should().BeTrue();
63+
value.Should().Be(3);
64+
}
65+
5766
[Fact]
5867
public void WhenNoInnerEventsNoOuterEvents()
5968
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryCacheTests.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
using System.Collections;
33
using System.Collections.Generic;
44
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
75
using BitFaster.Caching.Lru;
86
using BitFaster.Caching.Atomic;
97
using FluentAssertions;
@@ -54,6 +52,15 @@ public void WhenItemIsAddedThenLookedUpMetricsAreCorrect()
5452
this.cache.Metrics.Value.Hits.Should().Be(1);
5553
}
5654

55+
[Fact]
56+
public void WhenItemIsAddedWithArgValueIsCorrect()
57+
{
58+
this.cache.GetOrAdd(1, (k, a) => k + a, 2);
59+
60+
this.cache.TryGet(1, out var value).Should().BeTrue();
61+
value.Should().Be(3);
62+
}
63+
5764
[Fact]
5865
public void WhenRemovedEventHandlerIsRegisteredItIsFired()
5966
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryScopedAsyncCacheTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ public async Task WhenKeyDoesNotExistGetOrAddAsyncAddsValue()
4646
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
4747
}
4848

49+
[Fact]
50+
public async Task WhenKeyDoesNotExistGetOrAddArgAddsValueWithArg()
51+
{
52+
// TODO: move to base when interface supports factory arg
53+
var c = this.cache as AtomicFactoryScopedAsyncCache<int, Disposable>;
54+
55+
await c.ScopedGetOrAddAsync(
56+
1,
57+
(k, a) => Task.FromResult(new Scoped<Disposable>(new Disposable(a))),
58+
2);
59+
60+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
61+
lifetime.Value.State.Should().Be(2);
62+
}
63+
4964
[Fact]
5065
public async Task GetOrAddAsyncDisposedScopeThrows()
5166
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryScopedCacheTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,21 @@ public void WhenKeyDoesNotExistGetOrAddAddsValue()
4646
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
4747
}
4848

49+
[Fact]
50+
public void WhenKeyDoesNotExistGetOrAddArgAddsValueWithArg()
51+
{
52+
// TODO: move to base when interface supports factory arg
53+
var c = this.cache as AtomicFactoryScopedCache<int, Disposable>;
54+
55+
c.ScopedGetOrAdd(
56+
1,
57+
(k, a) => new Scoped<Disposable>(new Disposable(a)),
58+
2);
59+
60+
this.cache.ScopedTryGet(1, out var lifetime).Should().BeTrue();
61+
lifetime.Value.State.Should().Be(2);
62+
}
63+
4964
[Fact]
5065
public void GetOrAddDisposedScopeThrows()
5166
{

BitFaster.Caching.UnitTests/Atomic/AtomicFactoryTests.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
1+

52
using System.Threading;
63
using System.Threading.Tasks;
74
using BitFaster.Caching.Atomic;
@@ -40,6 +37,16 @@ public void WhenValueCreatedValueReturned()
4037
a.IsValueCreated.Should().BeTrue();
4138
}
4239

40+
[Fact]
41+
public void WhenValueCreatedWithArgValueReturned()
42+
{
43+
var a = new AtomicFactory<int, int>();
44+
a.GetValue(1, (k, a) => k + a, 7).Should().Be(8);
45+
46+
a.ValueIfCreated.Should().Be(8);
47+
a.IsValueCreated.Should().BeTrue();
48+
}
49+
4350
[Fact]
4451
public void WhenValueCreatedGetValueReturnsOriginalValue()
4552
{
@@ -48,6 +55,14 @@ public void WhenValueCreatedGetValueReturnsOriginalValue()
4855
a.GetValue(1, k => 3).Should().Be(2);
4956
}
5057

58+
[Fact]
59+
public void WhenValueCreatedArgGetValueReturnsOriginalValue()
60+
{
61+
var a = new AtomicFactory<int, int>();
62+
a.GetValue(1, (k, a) => k + a, 7);
63+
a.GetValue(1, (k, a) => k + a, 9).Should().Be(8);
64+
}
65+
5166
[Fact]
5267
public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
5368
{

BitFaster.Caching.UnitTests/Atomic/ScopedAsyncAtomicFactoryTests.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
52
using System.Threading;
63
using System.Threading.Tasks;
74
using BitFaster.Caching.Atomic;
@@ -62,6 +59,32 @@ public async Task WhenCreateFromValueLifetimeContainsValue()
6259
result.l.Value.actualNumber.Should().Be(1);
6360
}
6461

62+
[Fact]
63+
public async Task WhenCreateFromFactoryLifetimeContainsValue()
64+
{
65+
var atomicFactory = new ScopedAsyncAtomicFactory<int, IntHolder>();
66+
67+
(bool r, Lifetime<IntHolder> l) result = await atomicFactory.TryCreateLifetimeAsync(1, k =>
68+
{
69+
return Task.FromResult(new Scoped<IntHolder>(new IntHolder() { actualNumber = 2 }));
70+
});
71+
72+
result.r.Should().BeTrue();
73+
result.l.Value.actualNumber.Should().Be(2);
74+
}
75+
76+
[Fact]
77+
public async Task WhenCreateFromFactoryArgLifetimeContainsValue()
78+
{
79+
var atomicFactory = new ScopedAsyncAtomicFactory<int, IntHolder>();
80+
var factory = CreateArgFactory(7);
81+
82+
(bool r, Lifetime<IntHolder> l) result = await atomicFactory.TryCreateLifetimeAsync(1, factory);
83+
84+
result.r.Should().BeTrue();
85+
result.l.Value.actualNumber.Should().Be(8);
86+
}
87+
6588
[Fact]
6689
public async Task WhenScopeIsDisposedTryCreateReturnsFalse()
6790
{
@@ -201,6 +224,16 @@ public async Task WhenDisposedWhileThrowingNextInitIsDisposed()
201224
holder.disposed.Should().BeTrue();
202225
}
203226

227+
private static AsyncValueFactoryArg<int, int, Scoped<IntHolder>> CreateArgFactory(int arg)
228+
{
229+
return new AsyncValueFactoryArg<int, int, Scoped<IntHolder>>(
230+
(k, a) =>
231+
{
232+
return Task.FromResult(new Scoped<IntHolder>(new IntHolder() { actualNumber = k + a }));
233+
},
234+
arg);
235+
}
236+
204237
private class IntHolder : IDisposable
205238
{
206239
public bool disposed;

BitFaster.Caching.UnitTests/Atomic/ScopedAtomicFactoryTests.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
1+

62
using BitFaster.Caching.Atomic;
73
using FluentAssertions;
84
using Xunit;
@@ -45,6 +41,22 @@ public void WhenInitializedWithFactoryValueIsCached()
4541
lifetime2.Value.Should().Be(expectedDisposable);
4642
}
4743

44+
[Fact]
45+
public void WhenInitializedWithFactoryArgValueIsCached()
46+
{
47+
var expectedDisposable = new Disposable();
48+
var sa = new ScopedAtomicFactory<int, Disposable>();
49+
50+
var factory1 = new ValueFactoryArg<int, int, Scoped<Disposable>>((k, v) => { expectedDisposable.State = v; return new Scoped<Disposable>(expectedDisposable); }, 1);
51+
var factory2 = new ValueFactoryArg<int, int, Scoped<Disposable>>((k, v) => { expectedDisposable.State = v; return new Scoped<Disposable>(expectedDisposable); }, 2);
52+
53+
sa.TryCreateLifetime(1, factory1, out var lifetime1).Should().BeTrue();
54+
sa.TryCreateLifetime(1, factory2, out var lifetime2).Should().BeTrue();
55+
56+
lifetime2.Value.Should().Be(expectedDisposable);
57+
lifetime2.Value.State.Should().Be(1);
58+
}
59+
4860
[Fact]
4961
public void WhenScopeIsNotCreatedScopeIfCreatedReturnsNull()
5062
{

0 commit comments

Comments
 (0)