Skip to content
Merged
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
5 changes: 3 additions & 2 deletions NBitcoin.Bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

This project use BenchmarkDotnet to measure performance of parts of NBitcoin implementation.

You can generate flamegraph to view with Perfview on Windows with the following command:
You can generate flamegraph to view with [PerfView](https://github.com/microsoft/perfview) on Windows with the following command:

```powershell
dotnet run -c Release -- --runtimes net8.0 --filter *GolombRiceFilters* --profiler ETW
```

Where `GolombRiceFilters` is the name of the benchmark class you are insterested in.
Where `GolombRiceFilters` is the name of the benchmark class you are interested in.

For generating flamegraph on linux, see [this comment](https://github.com/MetacoSA/NBitcoin/pull/656#issuecomment-462927805).
35 changes: 35 additions & 0 deletions NBitcoin.Bench/UtilsBench.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using BenchmarkDotNet.Attributes;
using NBitcoin.DataEncoders;
using System.Collections.Generic;

namespace NBitcoin.Bench;

[MemoryDiagnoser]
public class UtilsBench
{
private static readonly byte[] taprootPubKeyA = Encoders.Hex.DecodeData("54c9e50dde41d5fc0b3dc0ff1ed4f6603a493d0a50dcc1badc3a5d959da7e750");
private static readonly byte[] taprootPubKeyB = Encoders.Hex.DecodeData("7ca556cf2fc5ba848c5ede3c8f0605dc78c005244915ad42b4b15076eb4b2c43");

public static IEnumerable<object[]> ArrayEqualArgs
{
get
{
yield return new byte[][] { [], [] }; // No bytes.
yield return new byte[][] { [], [0x01] }; // Empty array against non-empty array.
yield return new byte[][] { [0x01], [0x02] }; // 1-byte arrays which are not equal.
yield return new byte[][] { [0x01, 0x02], [0x01, 0x03] }; // 2-byte arrays which are not equal.
yield return new byte[][] { [0x01, 0x02, 0x03], [0x02, 0x03, 0x04] }; // 3-byte arrays which are not equal.
yield return new byte[][] { [0x01, 0x02, 0x03, 0x04, 0x05], [0x01, 0x02, 0x03, 0x04, 0x05] }; // 5-byte arrays which are equal.
yield return new byte[][] { [0x01, 0x02, 0x03, 0x04, 0x06, 0x05], [0x01, 0x02, 0x03, 0x04, 0x05, 0x06] }; // 6-byte arrays which are not equal.
yield return new byte[][] { [.. taprootPubKeyA], [.. taprootPubKeyA] }; // Create a copy to avoid reference equality
yield return new byte[][] { [.. taprootPubKeyA], [.. taprootPubKeyB] };
}
}

[Benchmark]
[ArgumentsSource(nameof(ArrayEqualArgs))]
public bool ArrayEqual(byte[] a, byte[] b)
{
return Utils.ArrayEqual(a, b);
}
}
2 changes: 1 addition & 1 deletion NBitcoin.Tests/util_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,7 @@ public void CanEncryptAndDecryptMessages()
Assert.True(Utils.ArrayEqual(text1, plainText));
Assert.Equal(text2, Encoders.ASCII.EncodeData(plainText));

// Encrypt twice, should not give twice same cypher
// Encrypt twice, should not give twice same cipher
var cipherText3 = key.PubKey.Encrypt(plainText);
Assert.True(!Utils.ArrayEqual(cipherText1, cipherText3));
}
Expand Down
18 changes: 10 additions & 8 deletions NBitcoin/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -471,24 +471,26 @@ internal static void SafeSet(ManualResetEvent ar)
}
catch { }
}
public static bool ArrayEqual(byte[] a, byte[] b)
public static bool ArrayEqual(byte[]? a, byte[]? b)
{
if (a == null && b == null)
return true;
if (a == null)
if (a == null || b == null)
return false;
if (b == null)

if (a.Length != b.Length)
return false;
return ArrayEqual(a, 0, b, 0, Math.Max(a.Length, b.Length));

return ArrayEqual(a, 0, b, 0, a.Length);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Originally, I was interested in

+#if HAS_SPAN
+			return a.AsSpan().SequenceEqual(b);
+#else
			return ArrayEqual(a, 0, b, 0, a.Length);
+#endif

but it behaves like this

Method a b Mean Error StdDev Ratio RatioSD Allocated Alloc Ratio
ArrayEqual_Master Byte[32] Byte[32] 22.2745 ns 0.4059 ns 0.4832 ns 24.86 0.64 - NA
ArrayEqual_PR Byte[32] Byte[32] 3.6044 ns 0.0907 ns 0.0848 ns 4.02 0.11 - NA
ArrayEqual_Master Byte[32] Byte[32] 1.3504 ns 0.0594 ns 0.0851 ns 1.51 0.10 - NA
ArrayEqual_PR Byte[32] Byte[32] 2.9751 ns 0.0841 ns 0.0826 ns 3.32 0.10 - NA

So for byte arrays that are equal it goes from 22 ns -> ~4 ns but it's worse for non-equal byte arrays. I guess non-equality is in general more common. One would have to go into "amortized time complexity"... well, I guess I cannot win with it.

}
public static bool ArrayEqual(byte[] a, int startA, byte[] b, int startB, int length)

public static bool ArrayEqual(byte[]? a, int startA, byte[]? b, int startB, int length)
{
if (a == null && b == null)
return true;
if (a == null)
return false;
if (b == null)
if (a == null || b == null)
return false;

var alen = a.Length - startA;
var blen = b.Length - startB;

Expand Down
Loading