Skip to content

Commit

Permalink
Add a simple benchmark. Spreads.LMDB is not only zero-copy, but zero-…
Browse files Browse the repository at this point in the history
…allocation as well and is optimized to be zero-overhead (e.g. .NET methods calls inlining).

Note that Spreads by default uses latest master on Windows that uses NTDLL API to grow env file on demand during writes, which is a bit slower but allows to set huge env map size and don't care about it later (same as Linux behavior). Therefore writes are marginally slower in Spreads master, but this is not the same native call.

Additionally, Spreads API is much more .NET friendly and supports generics and async out of the box. Binaries are prepacked into .NET distribution and do not require any additional steps on Linux.

## Benchmarks summary

There are two benchmarks: individual db put/get with a transaction per value and batched benchmark with 10M values per transaction. The first one demonstrates that Spreads.LMDB is zero-allocation and the second one shows mostly .NET overheads of calling the native lib. The MOPS column is million operations per second, GC columns are .NET garbage collection per generation during tests.

Using NO_SYNC to focus on wrapper performance and not disk speed.

## Benchmark with LMDB master with NTDLL API

**SimpleWriteReadBenchmark**: 10 rounds of appending 10M values and then reading them. Every value in individual transaction.

*SimpleWrite/Read 10x1M longs*

 Case                |    MOPS |  Elapsed |   GC0 |   GC1 |   GC2 |  Memory
--------------       |--------:|---------:|------:|------:|------:|--------:
Spreads Read         |    1.13 |   885 ms |   0.0 |   0.0 |   0.0 | 0.006 MB
KdSoft Read          |    1.08 |   927 ms |  68.0 |   0.0 |   0.0 | 4.011 MB
KdSoft Write         |    0.08 | 12580 ms |  61.0 |   1.0 |   0.0 | 0.320 MB
Spreads Write        |    0.08 | 13324 ms |   0.0 |   0.0 |   0.0 | 0.010 MB

## Benchmark using Release branch of LMDB without NTDLL API.

This is more relevant to compare .NET wrappers and not different native calls. Repeatable by replacing binary `libspreads_lmdb.dll.compressed` in `lib/out/w64` folder to the one built from LMDB release branch.

**SimpleWriteReadBenchmark**: 10 rounds of appending 10M values and then reading them. Every value in individual transaction.
*SimpleWrite/Read 10x1M longs*

 Case                |    MOPS |  Elapsed |   GC0 |   GC1 |   GC2 |  Memory
--------------       |--------:|---------:|------:|------:|------:|--------:
Spreads Read         |    1.12 |   896 ms |   0.0 |   0.0 |   0.0 | 0.008 MB
KdSoft Read          |    1.09 |   920 ms |  68.0 |   0.0 |   0.0 | 4.009 MB
Spreads Write        |    0.09 | 11763 ms |   0.0 |   0.0 |   0.0 | 0.009 MB
KdSoft Write         |    0.08 | 12745 ms |  61.0 |   1.0 |   0.0 | 0.321 MB

**SimpleWriteReadBatchedBenchmark**: 10M values per transaction. Spreads in 60% faster

*SimpleBatchedWrite/Read 10x1M longs*

 Case                |    MOPS |  Elapsed |   GC0 |   GC1 |   GC2 |  Memory
--------------       |--------:|---------:|------:|------:|------:|--------:
Spreads Read         |    7.14 |   140 ms |   0.0 |   0.0 |   0.0 | 0.000 MB
Spreads Write        |    6.99 |   143 ms |   0.0 |   0.0 |   0.0 | 0.001 MB
KdSoft Read          |    4.51 |   222 ms |   0.0 |   0.0 |   0.0 | 0.010 MB
KdSoft Write         |    4.22 |   237 ms |   0.0 |   0.0 |   0.0 | 0.011 MB

P.S. Will not compare to original Lightning.NET because they still [use `new byte[]`](https://github.com/CoreyKaylor/Lightning.NET/blob/8a8e5276108ee60c9ea6d9b2679bf8390baf3341/src/LightningDB/Native/ValueStructure.cs#L14) in the new era of `Span<T>`.
  • Loading branch information
buybackoff committed Sep 2, 2018
1 parent 3c27625 commit 4085dde
Show file tree
Hide file tree
Showing 2 changed files with 229 additions and 0 deletions.
227 changes: 227 additions & 0 deletions test/Spreads.LMDB.Tests/PerfTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

using KdSoft.Lmdb;
using NUnit.Framework;
using Spreads.Utils;
using System;
using System.IO;
using System.Runtime.InteropServices;

namespace Spreads.LMDB.Tests
{
[TestFixture]
public class PerfTests
{
[Test]
public unsafe void SimpleWriteReadBenchmark()
{
var count = 1_000_000;
var rounds = 10;

var path = "./data/benchmark";
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}

var dirS = Path.Combine(path, "Spreads");
var dirK = Path.Combine(path, "KdSoft");
Directory.CreateDirectory(dirS);
Directory.CreateDirectory(dirK);

var envS = LMDBEnvironment.Create(dirS, DbEnvironmentFlags.NoSync,
disableAsync: true);
envS.MaxDatabases = 10;
envS.MapSize = 256 * 1024 * 1024;
envS.Open();
var dbS = envS.OpenDatabase("SimpleWrite", new DatabaseConfig(DbFlags.Create | DbFlags.IntegerKey));

var envConfig = new EnvironmentConfiguration(10, mapSize: 256 * 1024 * 1024);
var envK = new KdSoft.Lmdb.Environment(envConfig);
envK.Open(dirK, EnvironmentOptions.NoSync);

var config = new DatabaseConfiguration(DatabaseOptions.Create | DatabaseOptions.IntegerKey);
KdSoft.Lmdb.Database dbase;
using (var tx = envK.BeginDatabaseTransaction(TransactionModes.None))
{
dbase = tx.OpenDatabase("SimpleWrite", config);
tx.Commit();
}

for (int r = 0; r < rounds; r++)
{
using (Benchmark.Run("Spreads Write", count, true))
{
for (long i = r * count; i < (r + 1) * count; i++)
{
using (var tx = envS.BeginTransaction())
{
dbS.Put(tx, i, i, TransactionPutOptions.AppendData);
tx.Commit();
}
}
}

using (Benchmark.Run("Spreads Read", count, true))
{
for (long i = r * count; i < (r + 1) * count; i++)
{
using (var tx = envS.BeginReadOnlyTransaction())
{
dbS.TryGet(tx, ref i, out long val);
if (val != i)
{
Assert.Fail();
}
}
}
}

using (Benchmark.Run("KdSoft Write", count, true))
{
for (long i = r * count; i < (r + 1) * count; i++)
{
var ptr = Unsafe.AsPointer(ref i);
var span = new Span<byte>(ptr, 8);

using (var tx = envK.BeginTransaction(TransactionModes.None))
{
dbase.Put(tx, span, span, PutOptions.AppendData);
tx.Commit();
}
}
}

using (Benchmark.Run("KdSoft Read", count, true))
{
for (long i = r * count; i < (r + 1) * count; i++)
{
var ptr = Unsafe.AsPointer(ref i);
var span = new Span<byte>(ptr, 8);

using (var tx = envK.BeginReadOnlyTransaction())
{
dbase.Get(tx, span, out var data);
var val = MemoryMarshal.Cast<byte, long>(data)[0];
if (val != i)
{
Assert.Fail();
}
}
}
}
}

envS.Close();
envK.Close();
Benchmark.Dump("SimpleWrite/Read 10x1M longs");
}

[Test]
public unsafe void SimpleWriteReadBatchedBenchmark()
{
var count = 1_000_000;
var rounds = 10;

var path = "./data/benchmarkbatched";
if (Directory.Exists(path))
{
Directory.Delete(path, true);
}

var dirS = Path.Combine(path, "Spreads");
var dirK = Path.Combine(path, "KdSoft");
Directory.CreateDirectory(dirS);
Directory.CreateDirectory(dirK);

var envS = LMDBEnvironment.Create(dirS, DbEnvironmentFlags.NoSync,
disableAsync: true);
envS.MaxDatabases = 10;
envS.MapSize = 256 * 1024 * 1024;
envS.Open();
var dbS = envS.OpenDatabase("SimpleWrite", new DatabaseConfig(DbFlags.Create | DbFlags.IntegerKey));

var envConfig = new EnvironmentConfiguration(10, mapSize: 256 * 1024 * 1024);
var envK = new KdSoft.Lmdb.Environment(envConfig);
envK.Open(dirK, EnvironmentOptions.NoSync);

var config = new DatabaseConfiguration(DatabaseOptions.Create | DatabaseOptions.IntegerKey);
KdSoft.Lmdb.Database dbase;
using (var tx = envK.BeginDatabaseTransaction(TransactionModes.None))
{
dbase = tx.OpenDatabase("SimpleWrite", config);
tx.Commit();
}

for (int r = 0; r < rounds; r++)
{
using (Benchmark.Run("Spreads Write", count, true))
{
using (var tx = envS.BeginTransaction())
{
for (long i = r * count; i < (r + 1) * count; i++)
{
dbS.Put(tx, i, i, TransactionPutOptions.AppendData);
}
tx.Commit();
}
}

using (Benchmark.Run("Spreads Read", count, true))
{
using (var tx = envS.BeginReadOnlyTransaction())
{
for (long i = r * count; i < (r + 1) * count; i++)
{
dbS.TryGet(tx, ref i, out long val);
if (val != i)
{
Assert.Fail();
}
}
}
}

using (Benchmark.Run("KdSoft Write", count, true))
{
using (var tx = envK.BeginTransaction(TransactionModes.None))
{
for (long i = r * count; i < (r + 1) * count; i++)
{
var ptr = Unsafe.AsPointer(ref i);
var span = new Span<byte>(ptr, 8);

dbase.Put(tx, span, span, PutOptions.AppendData);
}
tx.Commit();
}
}

using (Benchmark.Run("KdSoft Read", count, true))
{
using (var tx = envK.BeginReadOnlyTransaction())
{
for (long i = r * count; i < (r + 1) * count; i++)
{
var ptr = Unsafe.AsPointer(ref i);
var span = new Span<byte>(ptr, 8);

dbase.Get(tx, span, out var data);
var val = MemoryMarshal.Cast<byte, long>(data)[0];
if (val != i)
{
Assert.Fail();
}
}
}
}
}

envS.Close();
envK.Close();
Benchmark.Dump("SimpleBatchedWrite/Read 10x1M longs");
}
}
}
2 changes: 2 additions & 0 deletions test/Spreads.LMDB.Tests/Spreads.LMDB.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="KdSoft.Lmdb" Version="1.0.0-beta-2018-09-01" />
<PackageReference Include="LightningDB" Version="0.10.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0-preview-20180510-03" />
Expand Down

1 comment on commit 4085dde

@buybackoff
Copy link
Member Author

@buybackoff buybackoff commented on 4085dde Oct 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New numbers: 8f1e464

Please sign in to comment.