Skip to content
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
43 changes: 43 additions & 0 deletions BitFaster.Caching.Benchmarks/Lru/LruAsyncGet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BitFaster.Caching.Lru;

namespace BitFaster.Caching.Benchmarks.Lru
{
/// <summary>
/// Verify 0 allocs for GetOrAddAsync cache hits.
/// </summary>
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.Net60)]
[DisassemblyDiagnoser(printSource: true, maxDepth: 5)]
[MemoryDiagnoser]
public class LruAsyncGet
{
// if the cache value is a value type, value task has no effect - so use string to repro.
private static readonly IAsyncCache<int, string> concurrentLru = new ConcurrentLruBuilder<int, string>().AsAsyncCache().Build();
private static readonly IAsyncCache<int, string> atomicConcurrentLru = new ConcurrentLruBuilder<int, string>().AsAsyncCache().WithAtomicCreate().Build();

private static Task<string> returnTask = Task.FromResult("1");

[Benchmark()]
public async ValueTask<string> GetOrAddAsync()
{
Func<int, Task<string>> func = x => returnTask;

return await concurrentLru.GetOrAddAsync(1, func);
}

[Benchmark()]
public async ValueTask<string> AtomicGetOrAddAsync()
{
Func<int, Task<string>> func = x => returnTask;

return await atomicConcurrentLru.GetOrAddAsync(1, func);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
var result = 0;
var winnerCount = 0;

Task<int> first = atomicFactory.GetValueAsync(1, async k =>
var first = atomicFactory.GetValueAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand All @@ -80,7 +80,7 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
return 1;
});

Task<int> second = atomicFactory.GetValueAsync(1, async k =>
var second = atomicFactory.GetValueAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,6 @@ public void WhenKeyDoesNotExistGetOrAddAddsValue()
value.Should().Be(1);
}

[Fact]
public async Task GetOrAddAsyncThrows()
{
Func<Task> getOrAdd = async () => { await this.cache.GetOrAddAsync(1, k => Task.FromResult(k)); };

await getOrAdd.Should().ThrowAsync<NotImplementedException>();
}

[Fact]
public void WhenCacheContainsValuesTrim1RemovesColdestValue()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
var winningNumber = 0;
var winnerCount = 0;

Task<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
ValueTask<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand All @@ -108,7 +108,7 @@ public async Task WhenCallersRunConcurrentlyResultIsFromWinner()
return new Scoped<IntHolder>(new IntHolder() { actualNumber = 1 });
});

Task<(bool r, Lifetime<IntHolder> l)> second = atomicFactory.TryCreateLifetimeAsync(1, async k =>
ValueTask<(bool r, Lifetime<IntHolder> l)> second = atomicFactory.TryCreateLifetimeAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand Down Expand Up @@ -142,7 +142,7 @@ public async Task WhenDisposedWhileInitResultIsDisposed()
var atomicFactory = new ScopedAsyncAtomicFactory<int, IntHolder>();
var holder = new IntHolder() { actualNumber = 1 };

Task<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
ValueTask<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand Down Expand Up @@ -171,7 +171,7 @@ public async Task WhenDisposedWhileThrowingNextInitIsDisposed()
var atomicFactory = new ScopedAsyncAtomicFactory<int, IntHolder>();
var holder = new IntHolder() { actualNumber = 1 };

Task<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
ValueTask<(bool r, Lifetime<IntHolder> l)> first = atomicFactory.TryCreateLifetimeAsync(1, async k =>
{
enter.SetResult(true);
await resume.Task;
Expand Down
4 changes: 4 additions & 0 deletions BitFaster.Caching/BitFaster.Caching.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net472' or '$(TargetFramework)' == 'net48' or '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion BitFaster.Caching/IAsyncCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public interface IAsyncCache<K, V>
/// <param name="key">The key of the element to add.</param>
/// <param name="valueFactory">The factory function used to asynchronously generate a value for the key.</param>
/// <returns>A task that represents the asynchronous GetOrAdd operation.</returns>
Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory);
ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory);

/// <summary>
/// Attempts to remove the value that has the specified key.
Expand Down
2 changes: 1 addition & 1 deletion BitFaster.Caching/IScopedAsyncCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public interface IScopedAsyncCache<K, V> where V : IDisposable
/// <param name="key">The key of the element to add.</param>
/// <param name="valueFactory">The factory function used to asynchronously generate a scoped value for the key.</param>
/// <returns>A task that represents the asynchronous ScopedGetOrAdd operation.</returns>
Task<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory);
ValueTask<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory);

/// <summary>
/// Attempts to remove the value that has the specified key.
Expand Down
2 changes: 1 addition & 1 deletion BitFaster.Caching/Lru/ClassicLru.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public V GetOrAdd(K key, Func<K, V> valueFactory)
}

///<inheritdoc/>
public async Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
public async ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
{
if (this.TryGet(key, out var value))
{
Expand Down
2 changes: 1 addition & 1 deletion BitFaster.Caching/Lru/TemplateConcurrentLru.cs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ public V GetOrAdd(K key, Func<K, V> valueFactory)
}

///<inheritdoc/>
public async Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
public async ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
{
while (true)
{
Expand Down
2 changes: 1 addition & 1 deletion BitFaster.Caching/ScopedAsyncCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void Clear()
}

///<inheritdoc/>
public async Task<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
public async ValueTask<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
{
int c = 0;
var spinwait = new SpinWait();
Expand Down
6 changes: 3 additions & 3 deletions BitFaster.Caching/Synchronized/AsyncAtomicFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public AsyncAtomicFactory(V value)
this.value = value;
}

public async Task<V> GetValueAsync(K key, Func<K, Task<V>> valueFactory)
public async ValueTask<V> GetValueAsync(K key, Func<K, Task<V>> valueFactory)
{
if (initializer == null)
{
Expand All @@ -51,7 +51,7 @@ public V ValueIfCreated
}
}

private async Task<V> CreateValueAsync(K key, Func<K, Task<V>> valueFactory)
private async ValueTask<V> CreateValueAsync(K key, Func<K, Task<V>> valueFactory)
{
var init = initializer;

Expand All @@ -70,7 +70,7 @@ private class Initializer
private bool isInitialized;
private Task<V> valueTask;

public async Task<V> CreateValueAsync(K key, Func<K, Task<V>> valueFactory)
public async ValueTask<V> CreateValueAsync(K key, Func<K, Task<V>> valueFactory)
{
var tcs = new TaskCompletionSource<V>(TaskCreationOptions.RunContinuationsAsynchronously);

Expand Down
2 changes: 1 addition & 1 deletion BitFaster.Caching/Synchronized/AtomicFactoryAsyncCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void Clear()
cache.Clear();
}

public Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
public ValueTask<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
{
var synchronized = cache.GetOrAdd(key, _ => new AsyncAtomicFactory<K, V>());
return synchronized.GetValueAsync(key, valueFactory);
Expand Down
5 changes: 0 additions & 5 deletions BitFaster.Caching/Synchronized/AtomicFactoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ public V GetOrAdd(K key, Func<K, V> valueFactory)
return atomicFactory.GetValue(key, valueFactory);
}

public Task<V> GetOrAddAsync(K key, Func<K, Task<V>> valueFactory)
{
throw new NotImplementedException();
}

public void Trim(int itemCount)
{
this.cache.Trim(itemCount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void Clear()
this.cache.Clear();
}

public async Task<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
public async ValueTask<Lifetime<V>> ScopedGetOrAddAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
{
int c = 0;
var spinwait = new SpinWait();
Expand Down
6 changes: 3 additions & 3 deletions BitFaster.Caching/Synchronized/ScopedAsyncAtomicFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public bool TryCreateLifetime(out Lifetime<V> lifetime)
return scope.TryCreateLifetime(out lifetime);
}

public async Task<(bool success, Lifetime<V> lifetime)> TryCreateLifetimeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
public async ValueTask<(bool success, Lifetime<V> lifetime)> TryCreateLifetimeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
{
// if disposed, return
if (scope?.IsDisposed ?? false)
Expand All @@ -64,7 +64,7 @@ public bool TryCreateLifetime(out Lifetime<V> lifetime)
return (res, lifetime);
}

private async Task InitializeScopeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
private async ValueTask InitializeScopeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
{
var init = initializer;

Expand Down Expand Up @@ -97,7 +97,7 @@ private class Initializer
private bool isDisposeRequested;
private Task<Scoped<V>> task;

public async Task<Scoped<V>> CreateScopeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
public async ValueTask<Scoped<V>> CreateScopeAsync(K key, Func<K, Task<Scoped<V>>> valueFactory)
{
var tcs = new TaskCompletionSource<Scoped<V>>(TaskCreationOptions.RunContinuationsAsynchronously);

Expand Down