Skip to content

Commit

Permalink
[C#] Add initial Benchmarkdotnet tests (#751)
Browse files Browse the repository at this point in the history
* Add a couple BenchmarkDotNet tests

* add BenchmarkDotNet README

Co-authored-by: Badrish Chandramouli <badrishc@microsoft.com>
  • Loading branch information
TedHartMS and badrishc authored Oct 6, 2022
1 parent 4c6bd9d commit 134f41e
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,5 @@ nativebin/
cs/.idea/
cs/remote/.idea
cs/libdpr/.idea

cs/**/BenchmarkDotNet.Artifacts/
13 changes: 13 additions & 0 deletions cs/FASTER.sln
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TstRunner", "playground\Tst
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EpvsSample", "samples\EpvsSample\EpvsSample.csproj", "{DC3E0640-9A36-43D0-AA37-A1B61B0BFBC9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{5E4C9997-3350-4761-9FC9-F27649848B1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNetTests", "performance\BenchmarkDotNet\BenchmarkDotNetTests.csproj", "{AF996720-DB6C-4ED7-9693-B9531F0B119A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -311,6 +315,14 @@ Global
{80519947-DB86-4B21-8C11-B74186788BBD}.Release|Any CPU.Build.0 = Release|Any CPU
{80519947-DB86-4B21-8C11-B74186788BBD}.Release|x64.ActiveCfg = Release|Any CPU
{80519947-DB86-4B21-8C11-B74186788BBD}.Release|x64.Build.0 = Release|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Debug|x64.ActiveCfg = Debug|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Debug|x64.Build.0 = Debug|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Release|Any CPU.Build.0 = Release|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Release|x64.ActiveCfg = Release|Any CPU
{AF996720-DB6C-4ED7-9693-B9531F0B119A}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -344,6 +356,7 @@ Global
{E8C7FB0F-38B8-468A-B1CA-8793DF8F2693} = {E6026D6A-01C5-4582-B2C1-64751490DABE}
{A265D9D2-3FEA-48BB-B1CC-273ECFEA0611} = {E6026D6A-01C5-4582-B2C1-64751490DABE}
{DC3E0640-9A36-43D0-AA37-A1B61B0BFBC9} = {62BC1134-B6E1-476A-B894-7CA278A8B6DE}
{AF996720-DB6C-4ED7-9693-B9531F0B119A} = {5E4C9997-3350-4761-9FC9-F27649848B1D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A0750637-2CCB-4139-B25E-F2CE740DCFAC}
Expand Down
24 changes: 24 additions & 0 deletions cs/performance/BenchmarkDotNet/BenchmarkDotNetTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<DebugSymbols>true</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<Configuration>Release</Configuration>
<IsPackable>false</IsPackable>
<ApplicationIcon />
<OutputType>Exe</OutputType>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\core\FASTER.core.csproj" />
</ItemGroup>
</Project>
18 changes: 18 additions & 0 deletions cs/performance/BenchmarkDotNet/BenchmarkDotNetTestsApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using BenchmarkDotNet.Running;
using System.IO;

namespace BenchmarkDotNetTests
{
public class BenchmarkDotNetTestsApp
{
public static string TestDirectory => Path.Combine(Path.GetDirectoryName(typeof(BenchmarkDotNetTestsApp).Assembly.Location), "Tests");

public static void Main(string[] args)
{
BenchmarkSwitcher.FromAssembly(typeof(BenchmarkDotNetTestsApp).Assembly).Run(args);
}
}
}
29 changes: 29 additions & 0 deletions cs/performance/BenchmarkDotNet/LightEpochTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using FASTER.core;

#pragma warning disable 0649 // Field 'field' is never assigned to, and will always have its default value 'value'; happens due to [Params(..)]

namespace BenchmarkDotNetTests
{
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)]
public class LightEpochTests
{
[Params(10_000_000, 100_000_000, 1_000_000_000)]
public int NumIterations;

[BenchmarkCategory("LightEpoch"), Benchmark]
public void AcquireAndRelease()
{
var epoch = new LightEpoch();
for (int i = 0; i < NumIterations; i++)
{
epoch.Resume();
epoch.Suspend();
}
}
}
}
16 changes: 16 additions & 0 deletions cs/performance/BenchmarkDotNet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## BenchmarkDotNet tests

[BenchmarkDotNet](https://benchmarkdotnet.org) is a micro-benchmarking suite that supports annotations on functions, somewhat similar to unit tests.

To run:
- Build Release
- Execute `bin/Release/[platform]/BenchmarkDotNetTests.exe -f *LightEpoch*` to run the LightEpoch tests. See [BenchmarkDotNet](https://benchmarkdotnet.org) and related documentation for more details.

Currently we have two very simple benchmarks; more will be added later.
- LightEpochTests: Currently runs a microbenchmark of epoch Acquire/Release.
- SyncVsAsyncTests: Compares sync vs. async API performance.

BenchmarkDotNet does not support cross-run comparisons; currently, just save the output to a file and diff. A script will be written in the future to automate these comparisons.

### Adding a Test
Use one of the existing tests as an example, and add an entry in this doc describing it briefly.
135 changes: 135 additions & 0 deletions cs/performance/BenchmarkDotNet/SyncVsAsyncTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using FASTER.core;
using System;
using System.IO;
using System.Threading.Tasks;

#pragma warning disable 0649 // Field 'field' is never assigned to, and will always have its default value 'value'; happens due to [Params(..)]

namespace BenchmarkDotNetTests
{
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory, BenchmarkLogicalGroupRule.ByParams)]
public class SyncVsAsync
{
[Params(100, 1_000_000)]
public int NumRecords;

//[ParamsAllValues]
//bool useAsync;

FasterKV<long, long> store;
IDevice logDevice;
string logDirectory;

void SetupStore()
{
logDirectory = BenchmarkDotNetTestsApp.TestDirectory;
var logFilename = Path.Combine(logDirectory, $"{nameof(SyncVsAsync)}_{Guid.NewGuid()}.log");
logDevice = Devices.CreateLogDevice(logFilename, preallocateFile: true, deleteOnClose: true, useIoCompletionPort: true);
var logSettings = new LogSettings
{
LogDevice = logDevice
};
store = new FasterKV<long, long>(1L << 20, logSettings);
}

void PopulateStoreSync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
session.Upsert(ii, ii);
}

async ValueTask PopulateStoreAsync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
{
var result = await session.UpsertAsync(ii, ii);
while (result.Status.IsPending)
result = await result.CompleteAsync().ConfigureAwait(false);
}
}

[GlobalSetup(Targets = new[] { nameof(InsertSync), nameof(InsertAsync) })]
public void SetupEmptyStore() => SetupStore();

[GlobalSetup(Targets = new[] { nameof(RMWSync), nameof(RMWAsync), nameof(ReadSync), nameof(ReadAsync) })]
public void SetupPopulatedStore()
{
SetupStore();
PopulateStoreSync();
}

[GlobalCleanup]
public void TearDown()
{
store?.Dispose();
store = null;
logDevice?.Dispose();
logDevice = null;
try
{
Directory.Delete(logDirectory);
}
catch { }
}

[BenchmarkCategory("Insert"), Benchmark(Baseline = true)]
public void InsertSync() => PopulateStoreSync();

[BenchmarkCategory("Insert"), Benchmark]
public ValueTask InsertAsync() => PopulateStoreAsync();

[BenchmarkCategory("RMW"), Benchmark(Baseline = true)]
public void RMWSync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
session.RMW(ii, ii * 2);
session.CompletePending();
}

[BenchmarkCategory("RMW"), Benchmark]
public async ValueTask RMWAsync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
{
var result = await session.RMWAsync(ii, ii * 2);
while (result.Status.IsPending)
result = await result.CompleteAsync().ConfigureAwait(false);
}
}

[BenchmarkCategory("Read"), Benchmark(Baseline = true)]
public void ReadSync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
{
var (status, output) = session.Read(ii);
if (status.IsPending)
{
session.CompletePendingWithOutputs(out var completedOutputs, wait: true);
completedOutputs.Dispose();
}
}
session.CompletePending();
}

[BenchmarkCategory("Read"), Benchmark]
public async ValueTask ReadAsync()
{
using var session = store.For(new SimpleFunctions<long, long>()).NewSession<SimpleFunctions<long, long>>();
for (long ii = 0; ii < NumRecords; ++ii)
{
var (status, output) = (await session.ReadAsync(ii).ConfigureAwait(false)).Complete();
}
}
}
}
5 changes: 4 additions & 1 deletion cs/playground/AsyncStress/IFasterWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using FASTER.core;
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using FASTER.core;
using System.Threading.Tasks;

namespace AsyncStress
Expand Down

0 comments on commit 134f41e

Please sign in to comment.