diff --git a/CHANGES.md b/CHANGES.md index 75a319d..691a6da 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +## 0.0.9 (2023/05/16) +* Added maximum concurrency setting + ## 0.0.8 (2023/05/06) * CHANGED: build process * ADDED: GitHub actions diff --git a/src/Benchmarks/BatchBuilder1.cs b/src/Benchmarks/BatchBuilder1.cs new file mode 100644 index 0000000..312994e --- /dev/null +++ b/src/Benchmarks/BatchBuilder1.cs @@ -0,0 +1,55 @@ +using System.Collections.Concurrent; +using BenchmarkDotNet.Attributes; +using K4os.Async.Toys; + +namespace Benchmarks; + +public class BatchBuilder1 +{ + private const int Concurrency = 64; + private readonly SemaphoreSlim _semaphore = new(Concurrency); + private const int OpCount = 10000; + private readonly ConcurrentBag _bag = new(); + + private readonly IBatchBuilder _batchBuilder; + + public BatchBuilder1() + { + _batchBuilder = BatchBuilder.Create( + request => request, + response => response, + NoOpN, + new BatchBuilderSettings { BatchSize = 10, Concurrency = Concurrency }); + } + + [Benchmark] + public void Straight() + { + Task.WhenAll(Enumerable.Range(0, OpCount).Select(NoOp1)).Wait(); + } + + [Benchmark] + public void AsBatch() + { + Task.WhenAll(Enumerable.Range(0, OpCount).Select(_batchBuilder.Request)).Wait(); + } + + private async Task NoOpN(int[] requests) + { + await _semaphore.WaitAsync(); + await Delay(); + foreach (var request in requests) _bag.Add(request); + _semaphore.Release(); + return requests; + } + + private async Task NoOp1(int i) + { + await _semaphore.WaitAsync(); + await Delay(); + _bag.Add(i); + _semaphore.Release(); + } + + private static Task Delay() => Task.Delay(1); +} diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj new file mode 100644 index 0000000..d6a2644 --- /dev/null +++ b/src/Benchmarks/Benchmarks.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + + + diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs new file mode 100644 index 0000000..d11060a --- /dev/null +++ b/src/Benchmarks/Program.cs @@ -0,0 +1,5 @@ +// See https://aka.ms/new-console-template for more information + +using BenchmarkDotNet.Running; + +BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); diff --git a/src/K4os.Async.Toys.sln b/src/K4os.Async.Toys.sln index 52b6680..f8b676b 100644 --- a/src/K4os.Async.Toys.sln +++ b/src/K4os.Async.Toys.sln @@ -33,6 +33,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "K4os.Async.Toys.Tests", "K4 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "K4os.Async.Toys.App", "K4os.Async.Toys.App\K4os.Async.Toys.App.csproj", "{3E541988-CA33-477D-907E-DA43944AF400}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmarks", "Benchmarks\Benchmarks.csproj", "{BA1AD6AB-F72E-4A0C-ACA7-AA328C70CD59}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,10 @@ Global {3E541988-CA33-477D-907E-DA43944AF400}.Release|Any CPU.Build.0 = Release|Any CPU {17C2C146-01F7-4C31-89FF-F20958A8C147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17C2C146-01F7-4C31-89FF-F20958A8C147}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA1AD6AB-F72E-4A0C-ACA7-AA328C70CD59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA1AD6AB-F72E-4A0C-ACA7-AA328C70CD59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA1AD6AB-F72E-4A0C-ACA7-AA328C70CD59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA1AD6AB-F72E-4A0C-ACA7-AA328C70CD59}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/K4os.Async.Toys/AliveKeeper.cs b/src/K4os.Async.Toys/AliveKeeper.cs index 19db261..67bb158 100644 --- a/src/K4os.Async.Toys/AliveKeeper.cs +++ b/src/K4os.Async.Toys/AliveKeeper.cs @@ -96,7 +96,7 @@ public AliveKeeper( new BatchBuilderSettings { BatchSize = settings.TouchBatchSize, BatchDelay = settings.TouchBatchDelay, - Concurrency = 1, + Concurrency = settings.Concurrency, }, log, time); @@ -105,7 +105,7 @@ public AliveKeeper( new BatchBuilderSettings { BatchSize = settings.DeleteBatchSize, BatchDelay = TimeSpan.Zero, - Concurrency = 1, + Concurrency = settings.Concurrency, }, log, time); } @@ -118,6 +118,7 @@ private static IAliveKeeperSettings Validate(IAliveKeeperSettings settings) => DeleteBatchSize = settings.DeleteBatchSize.NotLessThan(1), RetryInterval = settings.RetryInterval.NotLessThan(TimeSpan.Zero), RetryLimit = settings.RetryLimit.NotLessThan(0), + Concurrency = settings.Concurrency.NotLessThan(1), }; private static T Pass(T x) => x; @@ -217,6 +218,7 @@ private async Task DeleteOne(string display, T item) /// Key. /// Key in human readable format. protected virtual string Display(T key) => + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract _keyToString?.Invoke(key) ?? key.ToString() ?? ""; private async Task TouchOneLoop(T item, CancellationToken token) diff --git a/src/K4os.Async.Toys/AliveKeeperSettings.cs b/src/K4os.Async.Toys/AliveKeeperSettings.cs index 05caafb..cfaf789 100644 --- a/src/K4os.Async.Toys/AliveKeeperSettings.cs +++ b/src/K4os.Async.Toys/AliveKeeperSettings.cs @@ -26,6 +26,9 @@ public interface IAliveKeeperSettings /// Number of retries for deletion. int RetryLimit { get; } + + /// Level of concurrency for operations. + int Concurrency { get; } } /// @@ -50,4 +53,7 @@ public class AliveKeeperSettings: IAliveKeeperSettings /// public int RetryLimit { get; set; } + + /// + public int Concurrency { get; set; } = 1; } \ No newline at end of file