Skip to content

Commit

Permalink
Add project for running microbenchmarks (#81)
Browse files Browse the repository at this point in the history
* Add project for running microbenchmerks

* Fix code style violations

* Update README.md

* Add link to BDN best practices to the benchmarking README

* Add OperationsPerInvoke to allow more fair comparison

* In the low-level read benchmarks, OperationsPerInvoke should amortize the small noise from the pinning
* In write benchmarks, OperationsPerInvoke should allow the comparing the benchmarks which use random values to the other constant integer benches

* Add default benchmark configuration with DPGO explicitly disabled for .NET 8

* Revert "Add OperationsPerInvoke to allow more fair comparison"

This reverts commit 2625443.

* Restore original TFMs to the benchmark project

The TFM is required for running benchmarks against it after all.

* Give the .NET 6 benchmark job a name

* Reorder imports per the .editorconfig

* Benchmark ascii length boundary values instead of random

* Fix benchmark commands in the README

* Rename BenchmarkDotNet.benchmark to BDN.benchmark
  • Loading branch information
PaulusParssinen committed Mar 25, 2024
1 parent 4a88c5a commit 934dad3
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 11 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,7 @@ test/tmp/

# IDE files
.vscode/
.idea/
.idea/

# BenchmarkDotNet Results
BenchmarkDotNet.Artifacts/
11 changes: 11 additions & 0 deletions Garnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClusterStress", "playground
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Embedded.perftest", "playground\Embedded.perftest\Embedded.perftest.csproj", "{5BEDAC1F-6458-4EBA-8174-EC06B07F2132}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDN.benchmark", "benchmark\BDN.benchmark\BDN.benchmark.csproj", "{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -244,6 +246,14 @@ Global
{5BEDAC1F-6458-4EBA-8174-EC06B07F2132}.Release|Any CPU.Build.0 = Release|Any CPU
{5BEDAC1F-6458-4EBA-8174-EC06B07F2132}.Release|x64.ActiveCfg = Release|Any CPU
{5BEDAC1F-6458-4EBA-8174-EC06B07F2132}.Release|x64.Build.0 = Release|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Debug|x64.ActiveCfg = Debug|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Debug|x64.Build.0 = Debug|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Release|Any CPU.Build.0 = Release|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Release|x64.ActiveCfg = Release|Any CPU
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -270,6 +280,7 @@ Global
{FE775048-3DB9-4F9E-A715-06C0E319BA63} = {9A03717A-4E0B-49CA-8579-A02A4C1D003F}
{8941A05C-099B-45AC-A7BF-F0E226BD59A8} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{5BEDAC1F-6458-4EBA-8174-EC06B07F2132} = {69A71E2C-00E3-42F3-854E-BE157A24834E}
{9F6E4734-6341-4A9C-A7FF-636A39D8BEAD} = {346A5A53-51E4-4A75-B7E6-491D950382CE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2C02C405-4798-41CA-AF98-61EDFEF6772E}
Expand Down
16 changes: 16 additions & 0 deletions benchmark/BDN.benchmark/BDN.benchmark.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\libs\common\Garnet.common.csproj" />
</ItemGroup>
</Project>
18 changes: 18 additions & 0 deletions benchmark/BDN.benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

var config = DefaultConfig.Instance
.AddJob(Job.Default
.WithRuntime(CoreRuntime.Core60)
.WithId(".NET 6"))
.AddJob(Job.Default
.WithRuntime(CoreRuntime.Core80)
.WithEnvironmentVariables(new EnvironmentVariable("DOTNET_TieredPGO", "0"))
.WithId(".NET 8"));

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
110 changes: 110 additions & 0 deletions benchmark/BDN.benchmark/Resp/RespIntegerReadBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Text;
using BenchmarkDotNet.Attributes;
using Garnet.common;

namespace BDN.benchmark.Resp
{
public unsafe class RespIntegerReadBenchmarks
{
[Benchmark]
[ArgumentsSource(nameof(SignedInt32EncodedValues))]
public int ReadInt32(AsciiTestCase testCase)
{
fixed (byte* inputPtr = testCase.Bytes)
{
var start = inputPtr;
RespReadUtils.ReadInt(out var value, ref start, start + testCase.Bytes.Length);
return value;
}
}

[Benchmark]
[ArgumentsSource(nameof(SignedInt64EncodedValues))]
public long ReadInt64(AsciiTestCase testCase)
{
fixed (byte* inputPtr = testCase.Bytes)
{
var start = inputPtr;
RespReadUtils.Read64Int(out var value, ref start, start + testCase.Bytes.Length);
return value;
}
}

[Benchmark]
[ArgumentsSource(nameof(SignedInt32EncodedValuesWithLengthHeader))]
public int ReadIntWithLengthHeader(AsciiTestCase testCase)
{
fixed (byte* inputPtr = testCase.Bytes)
{
var start = inputPtr;
RespReadUtils.ReadIntWithLengthHeader(out var value, ref start, start + testCase.Bytes.Length);
return value;
}
}

[Benchmark]
[ArgumentsSource(nameof(SignedInt64EncodedValuesWithLengthHeader))]
public long ReadLongWithLengthHeader(AsciiTestCase testCase)
{
fixed (byte* inputPtr = testCase.Bytes)
{
var start = inputPtr;
RespReadUtils.ReadLongWithLengthHeader(out var value, ref start, start + testCase.Bytes.Length);
return value;
}
}

[Benchmark]
[ArgumentsSource(nameof(UnsignedInt64EncodedValuesWithLengthHeader))]
public ulong ReadULongWithLengthHeader(AsciiTestCase testCase)
{
fixed (byte* inputPtr = testCase.Bytes)
{
var start = inputPtr;
RespReadUtils.ReadULongWithLengthHeader(out var value, ref start, start + testCase.Bytes.Length);
return value;
}
}

public static IEnumerable<object> SignedInt32EncodedValues
=> ToRespIntegerTestCases(RespIntegerWriteBenchmarks.SignedInt32Values);

public static IEnumerable<object> SignedInt64EncodedValues
=> ToRespIntegerTestCases(RespIntegerWriteBenchmarks.SignedInt64Values);

public static IEnumerable<object> UnsignedInt64EncodedValues
=> ToRespIntegerTestCases(RespIntegerWriteBenchmarks.UnsignedInt64Values);

public static IEnumerable<object> SignedInt32EncodedValuesWithLengthHeader
=> ToRespIntegerWithLengthHeader(RespIntegerWriteBenchmarks.SignedInt32Values);

public static IEnumerable<object> SignedInt64EncodedValuesWithLengthHeader
=> ToRespIntegerWithLengthHeader(RespIntegerWriteBenchmarks.SignedInt64Values);

public static IEnumerable<object> UnsignedInt64EncodedValuesWithLengthHeader
=> ToRespIntegerWithLengthHeader(RespIntegerWriteBenchmarks.UnsignedInt64Values);

public static IEnumerable<AsciiTestCase> ToRespIntegerTestCases<T>(T[] integerValues) where T : struct
=> integerValues.Select(testCase => new AsciiTestCase($":{testCase}\r\n"));

public static IEnumerable<AsciiTestCase> ToRespIntegerWithLengthHeader<T>(T[] integerValues) where T : struct
=> integerValues.Select(testCase => new AsciiTestCase($"${testCase.ToString()?.Length ?? 0}\r\n{testCase}\r\n"));

public sealed class AsciiTestCase
{
public byte[] Bytes { get; }
private string Text { get; }

public AsciiTestCase(string text)
{
Text = text;
Bytes = Encoding.ASCII.GetBytes(text);
}

public override string ToString() => Text; // displayed by BDN
}
}
}
104 changes: 104 additions & 0 deletions benchmark/BDN.benchmark/Resp/RespIntegerWriteBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using Garnet.common;

namespace BDN.benchmark.Resp
{
public unsafe class RespIntegerWriteBenchmarks
{
// Big enough buffer for the benchmarks
private readonly byte[] _buffer = new byte[32];

private byte* _bufferPtr;
private GCHandle _bufferHandle;

[GlobalSetup]
public void GlobalSetup()
{
// Pin the buffer for the benchmarks
_bufferHandle = GCHandle.Alloc(_buffer, GCHandleType.Pinned);
_bufferPtr = (byte*)_bufferHandle.AddrOfPinnedObject();
}

[GlobalCleanup]
public void GlobalCleanup() => _bufferHandle.Free();

[Benchmark]
[ArgumentsSource(nameof(SignedInt32Values))]
public bool WriteInt32(int value)
{
var startPtr = _bufferPtr;
return RespWriteUtils.WriteInteger(value, ref startPtr, _bufferPtr + _buffer.Length);
}

[Benchmark]
[ArgumentsSource(nameof(SignedInt64Values))]
public bool WriteInt64(long value)
{
var startPtr = _bufferPtr;
return RespWriteUtils.WriteInteger(value, ref startPtr, _bufferPtr + _buffer.Length);
}

[Benchmark]
[ArgumentsSource(nameof(SignedInt32Values))]
public bool WriteInt32AsBulkString(int value)
{
var startPtr = _bufferPtr;
return RespWriteUtils.WriteIntegerAsBulkString(value, ref startPtr, _bufferPtr + _buffer.Length);
}

[Benchmark]
public void WriteInt32_AllAsciiLengths()
{
for (int i = 0; i < SignedInt32MultiplesOfTen.Length; i++)
{
var startPtr = _bufferPtr;
RespWriteUtils.WriteInteger(SignedInt32MultiplesOfTen[i], ref startPtr, _bufferPtr + _buffer.Length);
}
}

[Benchmark]
public void WriteInt64_AllAsciiLengths()
{
for (int i = 0; i < SignedInt64MultiplesOfTen.Length; i++)
{
var startPtr = _bufferPtr;
RespWriteUtils.WriteInteger(SignedInt64MultiplesOfTen[i], ref startPtr, _bufferPtr + _buffer.Length);
}
}

[Benchmark]
public void WriteInt32BulkString_AllAsciiLengths()
{
for (int i = 0; i < SignedInt32MultiplesOfTen.Length; i++)
{
var startPtr = _bufferPtr;
RespWriteUtils.WriteIntegerAsBulkString(SignedInt32MultiplesOfTen[i], ref startPtr, _bufferPtr + _buffer.Length);
}
}

public const int UnsignedInt32MaxValueDigits = 10; // The number of digits in (u)int.MaxValue
public const int UnsignedInt64MaxValueDigits = 19; // The number of digits in (u)long.MaxValue

// All multiples of 10 from 10^-10 to 10^10
public static int[] SignedInt32MultiplesOfTen => [
..UnsignedInt32MultiplesOfTen.Select(n => n * -1),
..UnsignedInt32MultiplesOfTen
];
public static int[] UnsignedInt32MultiplesOfTen => Enumerable.Range(0, 10).Select(n => (int)Math.Pow(10, n)).ToArray();

// All multiples of 10 from 10^-19 to 10^19
public static long[] SignedInt64MultiplesOfTen => [
..UnsignedInt64MultiplesOfTen.Select(n => n * -1),
..UnsignedInt64MultiplesOfTen
];
public static long[] UnsignedInt64MultiplesOfTen => Enumerable.Range(0, 19).Select(n => (long)Math.Pow(10, n)).ToArray();

public static int[] SignedInt32Values => [int.MinValue, -1, 0, int.MaxValue];
public static long[] SignedInt64Values => [long.MinValue, -1, 0, long.MaxValue];
public static ulong[] UnsignedInt64Values => [0, int.MaxValue, ulong.MaxValue];
}
}
37 changes: 37 additions & 0 deletions benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Benchmarks

This directory contains projects for benchmarking Garnet.

## Resp.benchmark

**Garnet** project contains a Benchmark tool for running RESP benchmarking using different clients, different workloads and different strategies for measuring throughput, performance and latency.

Please visit our website documentation about how to use it in the following link: [The Resp.benchmark tool](https://microsoft.github.io/garnet/docs/benchmarking/resp-bench)

## BDN.benchmark

The `BDN.benchmark` command-line tool allows contributors to run precise and reproducible micro-benchmarks by utilizing the [BenchmarkDotNet](https://benchmarkdotnet.org/index.html).

### Usage

You can list all available benchmarks using `--list flat` or `--list tree`, e.g.

```
dotnet run -c Release -f net8.0 --list flat
```

To run specific benchmarks, you can use `--filter`. For example, to run all RESP-protocol write benchmarks using the default configuration, which will run the benchmarks using both .NET 6 and .NET 8 runtimes (with the dynamic PGO disabled):

```
dotnet run -c Release -f net8.0 --filter *RespIntegerWriteBenchmarks*
```

See more command-line options at https://benchmarkdotnet.org/articles/guides/console-args.html

### Writing microbenchmarks

Please see the [Microbenchmark Design Guidelines](https://github.com/dotnet/performance/blob/main/docs/microbenchmark-design-guidelines.md) for the best practices when writing microbenchmarks using BenchmarkDotNet.

## Privacy

[Microsoft Privacy Statement](https://go.microsoft.com/fwlink/?LinkId=521839)
10 changes: 0 additions & 10 deletions benchmark/Resp.benchmark/README.md

This file was deleted.

0 comments on commit 934dad3

Please sign in to comment.