Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ bin
artifacts/
results/
logs/
BenchmarkDotNet.Artifacts/
lmdb/lmdb/
testrun/
buildlog
Expand Down
82 changes: 82 additions & 0 deletions src/LightningDB.Benchmarks/ComparerBenchmarkBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.IO;
using BenchmarkDotNet.Attributes;

namespace LightningDB.Benchmarks;

/// <summary>
/// Base class for comparer benchmarks with configurable database setup.
/// Derived classes must add their own [ParamsSource] attribute for the Comparer property.
/// </summary>
public abstract class ComparerBenchmarkBase
{
private string _path;

protected LightningEnvironment Env { get; private set; }
protected LightningDatabase DB { get; private set; }

// Note: Derived classes must add [ParamsSource] attribute and override this property
public virtual ComparerDescriptor Comparer { get; set; }

[Params(100, 1000)]
public int OpsPerTransaction { get; set; }

[Params(64, 256)]
public int ValueSize { get; set; }

protected byte[] ValueBuffer { get; private set; }
protected KeyBatch KeyBuffers { get; private set; }

[GlobalSetup]
public void GlobalSetup()
{
Console.WriteLine($"Global Setup Begin - Comparer: {Comparer.Name}");

_path = $"BenchmarkDir_{Guid.NewGuid():N}";
if (Directory.Exists(_path))
Directory.Delete(_path, true);

Env = new LightningEnvironment(_path) { MaxDatabases = 1 };
Env.Open();

var config = new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create };
if (Comparer.Comparer != null)
config.CompareWith(Comparer.Comparer);

using (var tx = Env.BeginTransaction()) {
DB = tx.OpenDatabase(configuration: config);
tx.Commit();
}

ValueBuffer = new byte[ValueSize];
KeyBuffers = GenerateKeys();

RunSetup();

Console.WriteLine("Global Setup End");
}

protected virtual KeyBatch GenerateKeys()
=> KeyBatch.Generate(OpsPerTransaction, KeyOrdering.Sequential);

protected virtual void RunSetup() { }

[GlobalCleanup]
public void GlobalCleanup()
{
Console.WriteLine("Global Cleanup Begin");

try {
DB?.Dispose();
Env?.Dispose();

if (Directory.Exists(_path))
Directory.Delete(_path, true);
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}

Console.WriteLine("Global Cleanup End");
}
}
53 changes: 53 additions & 0 deletions src/LightningDB.Benchmarks/ComparerDescriptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using LightningDB.Comparers;

namespace LightningDB.Benchmarks;

/// <summary>
/// Wraps a comparer for BenchmarkDotNet parameterization with friendly display names.
/// </summary>
public readonly struct ComparerDescriptor
{
public string Name { get; }
public IComparer<MDBValue> Comparer { get; }

private ComparerDescriptor(string name, IComparer<MDBValue> comparer)
{
Name = name;
Comparer = comparer;
}

public override string ToString() => Name;

/// <summary>
/// All available comparers including Default (null = LMDB native).
/// </summary>
public static IEnumerable<ComparerDescriptor> All => new[]
{
new ComparerDescriptor("Default", null),
new ComparerDescriptor("Bitwise", BitwiseComparer.Instance),
new ComparerDescriptor("ReverseBitwise", ReverseBitwiseComparer.Instance),
new ComparerDescriptor("SignedInt", SignedIntegerComparer.Instance),
new ComparerDescriptor("ReverseSignedInt", ReverseSignedIntegerComparer.Instance),
new ComparerDescriptor("UnsignedInt", UnsignedIntegerComparer.Instance),
new ComparerDescriptor("ReverseUnsignedInt", ReverseUnsignedIntegerComparer.Instance),
new ComparerDescriptor("Utf8String", Utf8StringComparer.Instance),
new ComparerDescriptor("ReverseUtf8String", ReverseUtf8StringComparer.Instance),
new ComparerDescriptor("Length", LengthComparer.Instance),
new ComparerDescriptor("ReverseLength", ReverseLengthComparer.Instance),
new ComparerDescriptor("LengthOnly", LengthOnlyComparer.Instance),
new ComparerDescriptor("HashCode", HashCodeComparer.Instance),
};

/// <summary>
/// Integer comparers only (for focused integer key benchmarks).
/// </summary>
public static IEnumerable<ComparerDescriptor> IntegerComparers => new[]
{
new ComparerDescriptor("Default", null),
new ComparerDescriptor("SignedInt", SignedIntegerComparer.Instance),
new ComparerDescriptor("ReverseSignedInt", ReverseSignedIntegerComparer.Instance),
new ComparerDescriptor("UnsignedInt", UnsignedIntegerComparer.Instance),
new ComparerDescriptor("ReverseUnsignedInt", ReverseUnsignedIntegerComparer.Instance),
};
}
36 changes: 36 additions & 0 deletions src/LightningDB.Benchmarks/ComparerReadBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;

namespace LightningDB.Benchmarks;

/// <summary>
/// Benchmarks read operations across all comparers.
/// Reads use comparers for B-tree traversal during key lookups.
/// </summary>
[MemoryDiagnoser]
public class ComparerReadBenchmarks : ComparerBenchmarkBase
{
[ParamsSource(nameof(AllComparers))]
public override ComparerDescriptor Comparer { get; set; }

public static IEnumerable<ComparerDescriptor> AllComparers => ComparerDescriptor.All;

protected override void RunSetup()
{
// Pre-populate database for reads
using var tx = Env.BeginTransaction();
for (var i = 0; i < KeyBuffers.Count; i++)
tx.Put(DB, KeyBuffers[i], ValueBuffer);
tx.Commit();
}

[Benchmark]
public void Read()
{
using var tx = Env.BeginTransaction(TransactionBeginFlags.ReadOnly);

for (var i = 0; i < OpsPerTransaction; i++) {
_ = tx.Get(DB, KeyBuffers[i]);
}
}
}
29 changes: 29 additions & 0 deletions src/LightningDB.Benchmarks/ComparerWriteBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;

namespace LightningDB.Benchmarks;

/// <summary>
/// Benchmarks write operations across all comparers.
/// Write operations trigger B-tree insertions which invoke comparers frequently.
/// </summary>
[MemoryDiagnoser]
public class ComparerWriteBenchmarks : ComparerBenchmarkBase
{
[ParamsSource(nameof(AllComparers))]
public override ComparerDescriptor Comparer { get; set; }

public static IEnumerable<ComparerDescriptor> AllComparers => ComparerDescriptor.All;

[Benchmark]
public void Write()
{
using var tx = Env.BeginTransaction();

for (var i = 0; i < OpsPerTransaction; i++) {
tx.Put(DB, KeyBuffers[i], ValueBuffer);
}

tx.Commit();
}
}
107 changes: 107 additions & 0 deletions src/LightningDB.Benchmarks/IntegerComparerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;

namespace LightningDB.Benchmarks;

/// <summary>
/// Focused benchmarks for integer comparers testing their optimized 4-byte and 8-byte paths.
/// SignedIntegerComparer and UnsignedIntegerComparer have optimized fast paths for
/// int/uint (4 bytes) and long/ulong (8 bytes) that use direct memory reads.
/// </summary>
[MemoryDiagnoser]
public class IntegerComparerBenchmarks
{
private string _path;
private LightningEnvironment _env;
private LightningDatabase _db;
private byte[][] _keys;
private byte[] _valueBuffer;

[ParamsSource(nameof(IntegerComparers))]
public ComparerDescriptor Comparer { get; set; }

public static IEnumerable<ComparerDescriptor> IntegerComparers
=> ComparerDescriptor.IntegerComparers;

[Params(4, 8)]
public int KeySize { get; set; }

[Params(1000, 10000)]
public int OpsPerTransaction { get; set; }

[GlobalSetup]
public void GlobalSetup()
{
Console.WriteLine($"Global Setup Begin - Comparer: {Comparer.Name}, KeySize: {KeySize}");

_path = $"IntBenchDir_{Guid.NewGuid():N}";
if (Directory.Exists(_path))
Directory.Delete(_path, true);

_env = new LightningEnvironment(_path) { MaxDatabases = 1 };
_env.Open();

var config = new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create };
if (Comparer.Comparer != null)
config.CompareWith(Comparer.Comparer);

using (var tx = _env.BeginTransaction()) {
_db = tx.OpenDatabase(configuration: config);
tx.Commit();
}

_valueBuffer = new byte[64];
_keys = GenerateIntegerKeys(OpsPerTransaction, KeySize);

Console.WriteLine("Global Setup End");
}

private static byte[][] GenerateIntegerKeys(int count, int keySize)
{
var keys = new byte[count][];

for (var i = 0; i < count; i++) {
keys[i] = new byte[keySize];
if (keySize == 4)
MemoryMarshal.Write(keys[i], in i);
else // keySize == 8
MemoryMarshal.Write(keys[i], (long)i);
}

return keys;
}

[Benchmark]
public void WriteIntegers()
{
using var tx = _env.BeginTransaction();

for (var i = 0; i < OpsPerTransaction; i++) {
tx.Put(_db, _keys[i], _valueBuffer);
}

tx.Commit();
}

[GlobalCleanup]
public void GlobalCleanup()
{
Console.WriteLine("Global Cleanup Begin");

try {
_db?.Dispose();
_env?.Dispose();

if (Directory.Exists(_path))
Directory.Delete(_path, true);
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}

Console.WriteLine("Global Cleanup End");
}
}
30 changes: 18 additions & 12 deletions src/LightningDB.Benchmarks/KeyBatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum KeyOrdering
}

/// <summary>
/// A collection of 4 byte key arrays
/// A collection of key arrays with configurable size
/// </summary>
public class KeyBatch
{
Expand All @@ -28,16 +28,19 @@ private KeyBatch(byte[][] buffers)


public static KeyBatch Generate(int keyCount, KeyOrdering keyOrdering)
=> Generate(keyCount, keyOrdering, keySize: 4);

public static KeyBatch Generate(int keyCount, KeyOrdering keyOrdering, int keySize)
{
var buffers = new byte[keyCount][];

switch (keyOrdering) {
case KeyOrdering.Sequential:
PopulateSequential(buffers);
PopulateSequential(buffers, keySize);
break;

case KeyOrdering.Random:
PopulateRandom(buffers);
PopulateRandom(buffers, keySize);
break;

default:
Expand All @@ -47,14 +50,14 @@ public static KeyBatch Generate(int keyCount, KeyOrdering keyOrdering)
return new KeyBatch(buffers);
}

private static void PopulateSequential(byte[][] buffers)
private static void PopulateSequential(byte[][] buffers, int keySize)
{
for (var i = 0; i < buffers.Length; i++) {
buffers[i] = CopyToArray(i);
buffers[i] = CopyToArray(i, keySize);
}
}

private static void PopulateRandom(byte[][] buffers)
private static void PopulateRandom(byte[][] buffers, int keySize)
{
var random = new Random(0);
var seen = new HashSet<int>(buffers.Length);
Expand All @@ -63,17 +66,20 @@ private static void PopulateRandom(byte[][] buffers)
while (i < buffers.Length) {
var keyValue = random.Next(0, buffers.Length);

if (!seen.Add(keyValue))
if (!seen.Add(keyValue))
continue;//skip duplicates
buffers[i++] = CopyToArray(keyValue);

buffers[i++] = CopyToArray(keyValue, keySize);
}
}

private static byte[] CopyToArray(int keyValue)
private static byte[] CopyToArray(int keyValue, int keySize)
{
var key = new byte[4];
MemoryMarshal.Write(key, in keyValue);
var key = new byte[keySize];
if (keySize >= 8)
MemoryMarshal.Write(key, (long)keyValue);
else
MemoryMarshal.Write(key, in keyValue);
return key;
}
}
Loading