-
Notifications
You must be signed in to change notification settings - Fork 869
Optimize HexEncoder.DecodeData methods for .NET 8+
#1317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db31e62
e9ce8ab
a6597bb
49d403e
c3cefe3
0293327
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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; | ||
|
|
||
| [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); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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"); | ||
|
|
||
|
|
@@ -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) | ||
|
|
@@ -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); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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++) | ||
|
|
@@ -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) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/MetacoSA/NBitcoin/pull/1317/changes?w=1