Skip to content

Commit fc78d12

Browse files
committed
Adding comparer benchmarks
1 parent 741f3c3 commit fc78d12

8 files changed

Lines changed: 332 additions & 15 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ bin
55
artifacts/
66
results/
77
logs/
8+
BenchmarkDotNet.Artifacts/
89
lmdb/lmdb/
910
testrun/
1011
buildlog
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.IO;
3+
using BenchmarkDotNet.Attributes;
4+
5+
namespace LightningDB.Benchmarks;
6+
7+
/// <summary>
8+
/// Base class for comparer benchmarks with configurable database setup.
9+
/// Derived classes must add their own [ParamsSource] attribute for the Comparer property.
10+
/// </summary>
11+
public abstract class ComparerBenchmarkBase
12+
{
13+
private string _path;
14+
15+
protected LightningEnvironment Env { get; private set; }
16+
protected LightningDatabase DB { get; private set; }
17+
18+
// Note: Derived classes must add [ParamsSource] attribute and override this property
19+
public virtual ComparerDescriptor Comparer { get; set; }
20+
21+
[Params(100, 1000)]
22+
public int OpsPerTransaction { get; set; }
23+
24+
[Params(64, 256)]
25+
public int ValueSize { get; set; }
26+
27+
protected byte[] ValueBuffer { get; private set; }
28+
protected KeyBatch KeyBuffers { get; private set; }
29+
30+
[GlobalSetup]
31+
public void GlobalSetup()
32+
{
33+
Console.WriteLine($"Global Setup Begin - Comparer: {Comparer.Name}");
34+
35+
_path = $"BenchmarkDir_{Guid.NewGuid():N}";
36+
if (Directory.Exists(_path))
37+
Directory.Delete(_path, true);
38+
39+
Env = new LightningEnvironment(_path) { MaxDatabases = 1 };
40+
Env.Open();
41+
42+
var config = new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create };
43+
if (Comparer.Comparer != null)
44+
config.CompareWith(Comparer.Comparer);
45+
46+
using (var tx = Env.BeginTransaction()) {
47+
DB = tx.OpenDatabase(configuration: config);
48+
tx.Commit();
49+
}
50+
51+
ValueBuffer = new byte[ValueSize];
52+
KeyBuffers = GenerateKeys();
53+
54+
RunSetup();
55+
56+
Console.WriteLine("Global Setup End");
57+
}
58+
59+
protected virtual KeyBatch GenerateKeys()
60+
=> KeyBatch.Generate(OpsPerTransaction, KeyOrdering.Sequential);
61+
62+
protected virtual void RunSetup() { }
63+
64+
[GlobalCleanup]
65+
public void GlobalCleanup()
66+
{
67+
Console.WriteLine("Global Cleanup Begin");
68+
69+
try {
70+
DB?.Dispose();
71+
Env?.Dispose();
72+
73+
if (Directory.Exists(_path))
74+
Directory.Delete(_path, true);
75+
}
76+
catch (Exception ex) {
77+
Console.WriteLine(ex.ToString());
78+
}
79+
80+
Console.WriteLine("Global Cleanup End");
81+
}
82+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System.Collections.Generic;
2+
using LightningDB.Comparers;
3+
4+
namespace LightningDB.Benchmarks;
5+
6+
/// <summary>
7+
/// Wraps a comparer for BenchmarkDotNet parameterization with friendly display names.
8+
/// </summary>
9+
public readonly struct ComparerDescriptor
10+
{
11+
public string Name { get; }
12+
public IComparer<MDBValue> Comparer { get; }
13+
14+
private ComparerDescriptor(string name, IComparer<MDBValue> comparer)
15+
{
16+
Name = name;
17+
Comparer = comparer;
18+
}
19+
20+
public override string ToString() => Name;
21+
22+
/// <summary>
23+
/// All available comparers including Default (null = LMDB native).
24+
/// </summary>
25+
public static IEnumerable<ComparerDescriptor> All => new[]
26+
{
27+
new ComparerDescriptor("Default", null),
28+
new ComparerDescriptor("Bitwise", BitwiseComparer.Instance),
29+
new ComparerDescriptor("ReverseBitwise", ReverseBitwiseComparer.Instance),
30+
new ComparerDescriptor("SignedInt", SignedIntegerComparer.Instance),
31+
new ComparerDescriptor("ReverseSignedInt", ReverseSignedIntegerComparer.Instance),
32+
new ComparerDescriptor("UnsignedInt", UnsignedIntegerComparer.Instance),
33+
new ComparerDescriptor("ReverseUnsignedInt", ReverseUnsignedIntegerComparer.Instance),
34+
new ComparerDescriptor("Utf8String", Utf8StringComparer.Instance),
35+
new ComparerDescriptor("ReverseUtf8String", ReverseUtf8StringComparer.Instance),
36+
new ComparerDescriptor("Length", LengthComparer.Instance),
37+
new ComparerDescriptor("ReverseLength", ReverseLengthComparer.Instance),
38+
new ComparerDescriptor("LengthOnly", LengthOnlyComparer.Instance),
39+
new ComparerDescriptor("HashCode", HashCodeComparer.Instance),
40+
};
41+
42+
/// <summary>
43+
/// Integer comparers only (for focused integer key benchmarks).
44+
/// </summary>
45+
public static IEnumerable<ComparerDescriptor> IntegerComparers => new[]
46+
{
47+
new ComparerDescriptor("Default", null),
48+
new ComparerDescriptor("SignedInt", SignedIntegerComparer.Instance),
49+
new ComparerDescriptor("ReverseSignedInt", ReverseSignedIntegerComparer.Instance),
50+
new ComparerDescriptor("UnsignedInt", UnsignedIntegerComparer.Instance),
51+
new ComparerDescriptor("ReverseUnsignedInt", ReverseUnsignedIntegerComparer.Instance),
52+
};
53+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Collections.Generic;
2+
using BenchmarkDotNet.Attributes;
3+
4+
namespace LightningDB.Benchmarks;
5+
6+
/// <summary>
7+
/// Benchmarks read operations across all comparers.
8+
/// Reads use comparers for B-tree traversal during key lookups.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
public class ComparerReadBenchmarks : ComparerBenchmarkBase
12+
{
13+
[ParamsSource(nameof(AllComparers))]
14+
public override ComparerDescriptor Comparer { get; set; }
15+
16+
public static IEnumerable<ComparerDescriptor> AllComparers => ComparerDescriptor.All;
17+
18+
protected override void RunSetup()
19+
{
20+
// Pre-populate database for reads
21+
using var tx = Env.BeginTransaction();
22+
for (var i = 0; i < KeyBuffers.Count; i++)
23+
tx.Put(DB, KeyBuffers[i], ValueBuffer);
24+
tx.Commit();
25+
}
26+
27+
[Benchmark]
28+
public void Read()
29+
{
30+
using var tx = Env.BeginTransaction(TransactionBeginFlags.ReadOnly);
31+
32+
for (var i = 0; i < OpsPerTransaction; i++) {
33+
_ = tx.Get(DB, KeyBuffers[i]);
34+
}
35+
}
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System.Collections.Generic;
2+
using BenchmarkDotNet.Attributes;
3+
4+
namespace LightningDB.Benchmarks;
5+
6+
/// <summary>
7+
/// Benchmarks write operations across all comparers.
8+
/// Write operations trigger B-tree insertions which invoke comparers frequently.
9+
/// </summary>
10+
[MemoryDiagnoser]
11+
public class ComparerWriteBenchmarks : ComparerBenchmarkBase
12+
{
13+
[ParamsSource(nameof(AllComparers))]
14+
public override ComparerDescriptor Comparer { get; set; }
15+
16+
public static IEnumerable<ComparerDescriptor> AllComparers => ComparerDescriptor.All;
17+
18+
[Benchmark]
19+
public void Write()
20+
{
21+
using var tx = Env.BeginTransaction();
22+
23+
for (var i = 0; i < OpsPerTransaction; i++) {
24+
tx.Put(DB, KeyBuffers[i], ValueBuffer);
25+
}
26+
27+
tx.Commit();
28+
}
29+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Runtime.InteropServices;
5+
using BenchmarkDotNet.Attributes;
6+
7+
namespace LightningDB.Benchmarks;
8+
9+
/// <summary>
10+
/// Focused benchmarks for integer comparers testing their optimized 4-byte and 8-byte paths.
11+
/// SignedIntegerComparer and UnsignedIntegerComparer have optimized fast paths for
12+
/// int/uint (4 bytes) and long/ulong (8 bytes) that use direct memory reads.
13+
/// </summary>
14+
[MemoryDiagnoser]
15+
public class IntegerComparerBenchmarks
16+
{
17+
private string _path;
18+
private LightningEnvironment _env;
19+
private LightningDatabase _db;
20+
private byte[][] _keys;
21+
private byte[] _valueBuffer;
22+
23+
[ParamsSource(nameof(IntegerComparers))]
24+
public ComparerDescriptor Comparer { get; set; }
25+
26+
public static IEnumerable<ComparerDescriptor> IntegerComparers
27+
=> ComparerDescriptor.IntegerComparers;
28+
29+
[Params(4, 8)]
30+
public int KeySize { get; set; }
31+
32+
[Params(1000, 10000)]
33+
public int OpsPerTransaction { get; set; }
34+
35+
[GlobalSetup]
36+
public void GlobalSetup()
37+
{
38+
Console.WriteLine($"Global Setup Begin - Comparer: {Comparer.Name}, KeySize: {KeySize}");
39+
40+
_path = $"IntBenchDir_{Guid.NewGuid():N}";
41+
if (Directory.Exists(_path))
42+
Directory.Delete(_path, true);
43+
44+
_env = new LightningEnvironment(_path) { MaxDatabases = 1 };
45+
_env.Open();
46+
47+
var config = new DatabaseConfiguration { Flags = DatabaseOpenFlags.Create };
48+
if (Comparer.Comparer != null)
49+
config.CompareWith(Comparer.Comparer);
50+
51+
using (var tx = _env.BeginTransaction()) {
52+
_db = tx.OpenDatabase(configuration: config);
53+
tx.Commit();
54+
}
55+
56+
_valueBuffer = new byte[64];
57+
_keys = GenerateIntegerKeys(OpsPerTransaction, KeySize);
58+
59+
Console.WriteLine("Global Setup End");
60+
}
61+
62+
private static byte[][] GenerateIntegerKeys(int count, int keySize)
63+
{
64+
var keys = new byte[count][];
65+
66+
for (var i = 0; i < count; i++) {
67+
keys[i] = new byte[keySize];
68+
if (keySize == 4)
69+
MemoryMarshal.Write(keys[i], in i);
70+
else // keySize == 8
71+
MemoryMarshal.Write(keys[i], (long)i);
72+
}
73+
74+
return keys;
75+
}
76+
77+
[Benchmark]
78+
public void WriteIntegers()
79+
{
80+
using var tx = _env.BeginTransaction();
81+
82+
for (var i = 0; i < OpsPerTransaction; i++) {
83+
tx.Put(_db, _keys[i], _valueBuffer);
84+
}
85+
86+
tx.Commit();
87+
}
88+
89+
[GlobalCleanup]
90+
public void GlobalCleanup()
91+
{
92+
Console.WriteLine("Global Cleanup Begin");
93+
94+
try {
95+
_db?.Dispose();
96+
_env?.Dispose();
97+
98+
if (Directory.Exists(_path))
99+
Directory.Delete(_path, true);
100+
}
101+
catch (Exception ex) {
102+
Console.WriteLine(ex.ToString());
103+
}
104+
105+
Console.WriteLine("Global Cleanup End");
106+
}
107+
}

src/LightningDB.Benchmarks/KeyBatch.cs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public enum KeyOrdering
1111
}
1212

1313
/// <summary>
14-
/// A collection of 4 byte key arrays
14+
/// A collection of key arrays with configurable size
1515
/// </summary>
1616
public class KeyBatch
1717
{
@@ -28,16 +28,19 @@ private KeyBatch(byte[][] buffers)
2828

2929

3030
public static KeyBatch Generate(int keyCount, KeyOrdering keyOrdering)
31+
=> Generate(keyCount, keyOrdering, keySize: 4);
32+
33+
public static KeyBatch Generate(int keyCount, KeyOrdering keyOrdering, int keySize)
3134
{
3235
var buffers = new byte[keyCount][];
3336

3437
switch (keyOrdering) {
3538
case KeyOrdering.Sequential:
36-
PopulateSequential(buffers);
39+
PopulateSequential(buffers, keySize);
3740
break;
3841

3942
case KeyOrdering.Random:
40-
PopulateRandom(buffers);
43+
PopulateRandom(buffers, keySize);
4144
break;
4245

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

50-
private static void PopulateSequential(byte[][] buffers)
53+
private static void PopulateSequential(byte[][] buffers, int keySize)
5154
{
5255
for (var i = 0; i < buffers.Length; i++) {
53-
buffers[i] = CopyToArray(i);
56+
buffers[i] = CopyToArray(i, keySize);
5457
}
5558
}
5659

57-
private static void PopulateRandom(byte[][] buffers)
60+
private static void PopulateRandom(byte[][] buffers, int keySize)
5861
{
5962
var random = new Random(0);
6063
var seen = new HashSet<int>(buffers.Length);
@@ -63,17 +66,20 @@ private static void PopulateRandom(byte[][] buffers)
6366
while (i < buffers.Length) {
6467
var keyValue = random.Next(0, buffers.Length);
6568

66-
if (!seen.Add(keyValue))
69+
if (!seen.Add(keyValue))
6770
continue;//skip duplicates
68-
69-
buffers[i++] = CopyToArray(keyValue);
71+
72+
buffers[i++] = CopyToArray(keyValue, keySize);
7073
}
7174
}
7275

73-
private static byte[] CopyToArray(int keyValue)
76+
private static byte[] CopyToArray(int keyValue, int keySize)
7477
{
75-
var key = new byte[4];
76-
MemoryMarshal.Write(key, in keyValue);
78+
var key = new byte[keySize];
79+
if (keySize >= 8)
80+
MemoryMarshal.Write(key, (long)keyValue);
81+
else
82+
MemoryMarshal.Write(key, in keyValue);
7783
return key;
7884
}
7985
}

0 commit comments

Comments
 (0)