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
72 changes: 48 additions & 24 deletions NBitcoin.Bench/HexBench.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,56 @@
using BenchmarkDotNet.Attributes;
using NBitcoin.DataEncoders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NBitcoin.Bench
namespace NBitcoin.Bench;

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.


[MemoryDiagnoser]
public class HexBench
{
public class HexBench
const string str = "d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a";
const string uint256_str = "00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
string random_str_10kb;

private byte[] bytes;

[GlobalSetup]
public void Setup()
{
bytes = Encoders.Hex.DecodeData(str);

var randomBytes = new byte[10 * 1024];
Random.Shared.NextBytes(randomBytes);
random_str_10kb = Encoders.Hex.EncodeData(randomBytes);
}

[Benchmark]
public void DecodeData()
{
Encoders.Hex.DecodeData(str);
}

[Benchmark]
[Arguments(uint256_str)] // 32 bytes
[Arguments(uint256_str + uint256_str)] // 64 bytes
[Arguments(uint256_str + uint256_str + uint256_str)] // 96 bytes
public void DecodeDataSpan(string hexString)
{
Span<byte> tmp = stackalloc byte[hexString.Length / 2];
HexEncoder hex = (HexEncoder)Encoders.Hex;
hex.DecodeData(hexString, tmp);
}

[Benchmark]
public void DecodeDataLongSpan()
{
Span<byte> tmp = stackalloc byte[random_str_10kb.Length / 2];
HexEncoder hex = (HexEncoder)Encoders.Hex;
hex.DecodeData(random_str_10kb, tmp);
}

[Benchmark]
public void EncodeData()
{
string str = "d54994ece1d11b19785c7248868696250ab195605b469632b7bd68130e880c9a";
private byte[] bytes;

[GlobalSetup]
public void Setup()
{
bytes = Encoders.Hex.DecodeData(str);
}
[Benchmark]
public void Decode()
{
Encoders.Hex.DecodeData(str);
}
[Benchmark]
public void Encode()
{
Encoders.Hex.EncodeData(bytes);
}
Encoders.Hex.EncodeData(bytes);
}
}
6 changes: 1 addition & 5 deletions NBitcoin.Tests/uint256_tests.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using NBitcoin.Protocol;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace NBitcoin.Tests
Expand Down
15 changes: 14 additions & 1 deletion NBitcoin/DataEncoders/HexEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public override string EncodeData(byte[] data, int offset, int count)
if (data == null)
throw new ArgumentNullException(nameof(data));

#if NET6_0_OR_GREATER
#if NET8_0_OR_GREATER
// TODO: Convert.ToHexStringLower (.NET 9+) once available.
return Convert.ToHexString(data, offset, count).ToLowerInvariant();
#elif !HAS_SPAN
int pos = 0;
Expand Down Expand Up @@ -98,6 +99,10 @@ public override byte[] DecodeData(string encoded)
{
if (encoded == null)
throw new ArgumentNullException(nameof(encoded));

#if NET8_0_OR_GREATER
return Convert.FromHexString(encoded);
#else
if (encoded.Length % 2 == 1)
throw new FormatException("Invalid Hex String");

Expand All @@ -111,6 +116,7 @@ public override byte[] DecodeData(string encoded)
result[j] = (byte)((hi << 4) | lo);
}
return result;
#endif
}
#if HAS_SPAN
public void DecodeData(string encoded, Span<byte> output)
Expand All @@ -121,6 +127,12 @@ public void DecodeData(string encoded, Span<byte> output)
throw new FormatException("Invalid Hex String");
if (output.Length < (encoded.Length >> 1))
throw new ArgumentException("output should be bigger", nameof(output));

#if NET8_0_OR_GREATER
var decoded = Convert.FromHexString(encoded);

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.

Even though this allocates a byte array, it's still faster to use Convert.FromHexString because it uses SIMD instructions.

@Carti-it Carti-it Jun 14, 2026

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.

An alternative would be to target NET 10 TFM (along with NET 8.0) by NBitcoin and just use Convert.FromHexString(string source, Span<byte> destination). I'm not sure if it's worth it and how much work it would require (mostly updating a CI script and a csproj file I guess).

decoded.CopyTo(output);
#else
// TODO: Convert.FromHexString(string source, Span<byte> destination) (.NET 10+) once available.
try
{
for (int i = 0, j = 0; i < encoded.Length; i += 2, j++)
Expand All @@ -133,6 +145,7 @@ public void DecodeData(string encoded, Span<byte> output)
}
}
catch(IndexOutOfRangeException) { throw new FormatException("Invalid Hex String"); }
#endif
}
#endif
public bool IsValid(string str)
Expand Down
Loading