Skip to content

Commit a2e7da3

Browse files
committed
feat: add runtime monicker .net10 for benchmarking
1 parent ce30810 commit a2e7da3

File tree

4 files changed

+159
-28
lines changed

4 files changed

+159
-28
lines changed

src/Base58Encoding.Benchmarks/Base58ComparisonBenchmark.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Columns;
23
using BenchmarkDotNet.Diagnosers;
34
using BenchmarkDotNet.Jobs;
45
using Base58Encoding.Benchmarks.Common;
56

67
namespace Base58Encoding.Benchmarks;
78

89
[SimpleJob(RuntimeMoniker.Net90)]
10+
[SimpleJob(RuntimeMoniker.Net10_0)]
911
[MemoryDiagnoser]
1012
[DisassemblyDiagnoser(exportCombinedDisassemblyReport: true)]
13+
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
1114
public class Base58ComparisonBenchmark
1215
{
1316
private byte[] _testData = null!;

src/Base58Encoding.Benchmarks/BoundsCheckComparisonBenchmark.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Columns;
23
using BenchmarkDotNet.Diagnosers;
34
using BenchmarkDotNet.Jobs;
45
using System.Runtime.CompilerServices;
@@ -8,7 +9,9 @@
89
namespace Base58Encoding.Benchmarks;
910

1011
[SimpleJob(RuntimeMoniker.Net90)]
12+
[SimpleJob(RuntimeMoniker.Net10_0)]
1113
[DisassemblyDiagnoser(exportCombinedDisassemblyReport: true)]
14+
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
1215
public class BoundsCheckComparisonBenchmark
1316
{
1417
private const int Base = 58;

src/Base58Encoding.Benchmarks/CountLeadingZerosBenchmark.cs

Lines changed: 151 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,49 @@
11
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Columns;
3+
using BenchmarkDotNet.Jobs;
4+
using System.Collections.Generic;
5+
using System.Numerics;
26
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
8+
using System.Runtime.Intrinsics;
39

410
namespace Base58Encoding.Benchmarks;
511

12+
[SimpleJob(RuntimeMoniker.Net90)]
13+
[SimpleJob(RuntimeMoniker.Net10_0)]
14+
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
615
public class CountLeadingZerosBenchmark
716
{
817
private static Lazy<byte[][]> _lazyTestData = new(() =>
918
{
10-
// Decode the provided base58 addresses to get byte arrays with leading zeros
11-
var addresses = new[]
12-
{
13-
"1A1zP12r2r2r1wq1",
14-
"1111111111111111111114oLvT2",
15-
"1111113LrYkRba5STgvA5UoLqzLxwP6XhtN6f",
16-
"1BoatSLRHtKNngkdXEeobR76b53LETtpyT",
17-
"11bJr6xq2UhxDqkdqNKoGPYYEVBy6cd3M"
18-
};
19-
var data = new byte[addresses.Length][];
20-
for (int i = 0; i < addresses.Length; i++)
21-
{
22-
data[i] = Base58.Bitcoin.Decode(addresses[i]);
23-
}
24-
return data;
25-
});
19+
var testCases = new List<byte[]>();
20+
21+
// Bitcoin addresses with varying leading zeros
22+
testCases.Add(Base58.Bitcoin.Decode("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")); // 0 leading zeros (standard P2PKH)
23+
testCases.Add(Base58.Bitcoin.Decode("1111111111111111111114oLvT2")); // Many leading zeros
24+
testCases.Add(Base58.Bitcoin.Decode("1BoatSLRHtKNngkdXEeobR76b53LETtpyT")); // 0 leading zeros (standard)
25+
26+
// Solana addresses (32 bytes) - realistic cases
27+
testCases.Add(Base58.Bitcoin.Decode("11111111111111111111111111111112")); // 31 leading zeros (system program)
28+
testCases.Add(Base58.Bitcoin.Decode("1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM")); // 1 leading zero
29+
testCases.Add(Base58.Bitcoin.Decode("4TMFNY21gmHLT3HpPZDiXY6kQkGPpJsJRfisJ9T7rXdV")); // Typical Solana address (0 leading zeros)
30+
testCases.Add(Base58.Bitcoin.Decode("So11111111111111111111111111111111111111112")); // Wrapped SOL (0 leading zeros)
31+
32+
// IPFS hashes (46 bytes) - typical CIDv0
33+
testCases.Add(Base58.Bitcoin.Decode("QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG")); // No leading zeros (typical)
2634

27-
public byte[][] TestData;
35+
// Sui transaction digests (32 bytes)
36+
testCases.Add(Base58.Bitcoin.Decode("11111118YbNzKyoNPP4TJLYwpB2B3NCvpNXJbRNcszU")); // Many leading zeros
37+
testCases.Add(Base58.Bitcoin.Decode("3aDawfe6rm6QnFhJGppyyHVxXfEhBGYPMD8nzfLKNJqq"));
2838

39+
// Typical blockchain transaction hashes
40+
testCases.Add(Base58.Bitcoin.Decode("4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi")); // 0 leading zeros
41+
testCases.Add(Base58.Bitcoin.Decode("11BxRVsYgGSUZRSMaNcNjSzwMBYXqZQvBQr3BQznBFmpV")); // 2 leading zeros
42+
43+
return testCases.ToArray();
44+
});
45+
46+
private byte[][] TestData = default!;
2947

3048
[GlobalSetup]
3149
public void Setup()
@@ -50,7 +68,7 @@ public int Scalar()
5068
int totalCount = 0;
5169
foreach (var data in TestData)
5270
{
53-
totalCount += Base58.CountLeadingZerosScalar(data);
71+
totalCount += CountLeadingZerosScalarImpl(data);
5472
}
5573
return totalCount;
5674
}
@@ -61,13 +79,15 @@ public int SimdOnly()
6179
int totalCount = 0;
6280
foreach (var data in TestData)
6381
{
64-
int count = Base58.CountLeadingZerosSimd(data, out int processed);
82+
int count = CountLeadingZerosSimdImpl(data, out int processed);
6583
if (count < processed)
66-
return count;
67-
68-
var total = count + Base58.CountLeadingZerosScalar(data.AsSpan(count));
69-
70-
totalCount += total;
84+
{
85+
totalCount += count;
86+
}
87+
else
88+
{
89+
totalCount += count + CountLeadingZerosScalarImpl(data.AsSpan(count));
90+
}
7191
}
7292

7393
return totalCount;
@@ -79,12 +99,11 @@ public int Combined()
7999
int totalCount = 0;
80100
foreach (var data in TestData)
81101
{
82-
totalCount += Base58.CountLeadingZeros(data);
102+
totalCount += CountLeadingZerosCombinedImpl(data);
83103
}
84104
return totalCount;
85105
}
86106

87-
// Simple array-based approach
88107
[MethodImpl(MethodImplOptions.AggressiveInlining)]
89108
private static int CountLeadingZerosArray(ReadOnlySpan<byte> data)
90109
{
@@ -97,4 +116,110 @@ private static int CountLeadingZerosArray(ReadOnlySpan<byte> data)
97116
}
98117
return count;
99118
}
119+
120+
// Copy of Base58.CountLeadingZerosScalar implementation
121+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
122+
private static int CountLeadingZerosScalarImpl(ReadOnlySpan<byte> data)
123+
{
124+
int count = 0;
125+
int length = data.Length;
126+
ref byte searchSpace = ref MemoryMarshal.GetReference(data);
127+
128+
while (length >= sizeof(ulong))
129+
{
130+
ulong value = Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref searchSpace, count));
131+
if (value != 0)
132+
{
133+
if (BitConverter.IsLittleEndian)
134+
{
135+
count += BitOperations.TrailingZeroCount(value) / 8;
136+
}
137+
else
138+
{
139+
count += BitOperations.LeadingZeroCount(value) / 8;
140+
}
141+
return count;
142+
}
143+
count += sizeof(ulong);
144+
length -= sizeof(ulong);
145+
}
146+
147+
while (count < data.Length && data[count] == 0)
148+
{
149+
count++;
150+
}
151+
152+
return count;
153+
}
154+
155+
// Copy of Base58.CountLeadingZerosSimd implementation
156+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
157+
private static int CountLeadingZerosSimdImpl(ReadOnlySpan<byte> data, out int processed)
158+
{
159+
int count = 0;
160+
int length = data.Length;
161+
ref byte searchSpace = ref MemoryMarshal.GetReference(data);
162+
163+
if (Vector256.IsHardwareAccelerated && length >= Vector256<byte>.Count)
164+
{
165+
var zeroVector = Vector256<byte>.Zero;
166+
167+
while (length >= Vector256<byte>.Count)
168+
{
169+
var vector = Vector256.LoadUnsafe(ref searchSpace, (nuint)count);
170+
var comparison = Vector256.Equals(vector, zeroVector);
171+
uint mask = comparison.ExtractMostSignificantBits();
172+
173+
if (mask != uint.MaxValue)
174+
{
175+
processed = count + Vector256<byte>.Count;
176+
return count + BitOperations.TrailingZeroCount(~mask);
177+
}
178+
179+
count += Vector256<byte>.Count;
180+
length -= Vector256<byte>.Count;
181+
}
182+
}
183+
else if (Vector128.IsHardwareAccelerated && length >= Vector128<byte>.Count)
184+
{
185+
var zeroVector = Vector128<byte>.Zero;
186+
187+
while (length >= Vector128<byte>.Count)
188+
{
189+
var vector = Vector128.LoadUnsafe(ref searchSpace, (nuint)count);
190+
var comparison = Vector128.Equals(vector, zeroVector);
191+
uint mask = comparison.ExtractMostSignificantBits();
192+
193+
if (mask != ushort.MaxValue)
194+
{
195+
processed = count + Vector128<byte>.Count;
196+
return count + BitOperations.TrailingZeroCount(~mask);
197+
}
198+
199+
count += Vector128<byte>.Count;
200+
length -= Vector128<byte>.Count;
201+
}
202+
}
203+
204+
processed = count;
205+
return count;
206+
}
207+
208+
// Copy of Base58.CountLeadingZeros implementation
209+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
210+
private static int CountLeadingZerosCombinedImpl(ReadOnlySpan<byte> data)
211+
{
212+
// intentionally commenting out, since other methods won't have it also
213+
//if (data.Length == 0)
214+
// return 0;
215+
216+
if (data.Length < 16)
217+
return CountLeadingZerosScalarImpl(data);
218+
219+
int count = CountLeadingZerosSimdImpl(data, out int processed);
220+
if (count < processed)
221+
return count;
222+
223+
return count + CountLeadingZerosScalarImpl(data.Slice(count));
224+
}
100225
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using BenchmarkDotNet.Running;
22
using Base58Encoding.Benchmarks;
33

4-
BenchmarkRunner.Run<Base58ComparisonBenchmark>();
5-
//BenchmarkRunner.Run<CountLeadingZerosBenchmark>();
4+
//BenchmarkRunner.Run<Base58ComparisonBenchmark>();
5+
BenchmarkRunner.Run<CountLeadingZerosBenchmark>();
66
//BenchmarkRunner.Run<BoundsCheckComparisonBenchmark>();

0 commit comments

Comments
 (0)