Skip to content
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

Optimize the ToHexString #3566

Merged
merged 12 commits into from
Nov 8, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Neo.Extensions\Neo.Extensions.csproj" />
</ItemGroup>

</Project>
9 changes: 5 additions & 4 deletions src/Neo.Cryptography.BLS12_381/Scalar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using Neo.Extensions;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -119,11 +120,11 @@ private Span<ulong> GetSpanU64()

public override string ToString()
{
byte[] bytes = ToArray();
StringBuilder sb = new();
var bytes = ToArray();

StringBuilder sb = new(2 + (bytes.Length * 2));
shargon marked this conversation as resolved.
Show resolved Hide resolved
sb.Append("0x");
for (int i = bytes.Length - 1; i >= 0; i--)
sb.AppendFormat("{0:x2}", bytes[i]);
sb.Append(bytes.ToHexString(true));
return sb.ToString();
}

Expand Down
54 changes: 43 additions & 11 deletions src/Neo.Extensions/ByteExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,36 @@
// modifications are permitted.

using System;
using System.Runtime.CompilerServices;
using System.Text;

namespace Neo.Extensions
{
public static class ByteExtensions
{
private const string s_hexChars = "0123456789abcdef";
Copy link
Member

Choose a reason for hiding this comment

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

How can be const string faster than char string?

Copy link
Member

Choose a reason for hiding this comment

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

If access to the char of a string is faster than access to the char array, the benchmark was wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If access to the char of a string is faster than access to the char array, the benchmark was wrong

Const value may more friendly for compiler and JIT optimization.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If access to the char of a string is faster than access to the char array, the benchmark was wrong

UTF-8 string is slower, but this string just asc-ii, so it can be optimized.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

@nan01ab nan01ab Nov 8, 2024

Choose a reason for hiding this comment

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

https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/String.cs#L750-L753

Wrong benchmark, i'm sure

Why this is slower, there are just two operations: an if check and a pointer add(a lea instruction on x86), and it has an Intrinsic attribute, so JIT can optimize it.

There are also a bound check(for IndexOutOfRangeException) and a pointer computation on array access.

Copy link
Member

@cschuchardt88 cschuchardt88 Nov 8, 2024

Choose a reason for hiding this comment

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

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables#dotnet_jit-and-dotnet_gc
https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/jit/investigate-stress.md#jit-stress-debug-builds-only--debug-and-checked

This is the one you want
https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/jit/viewing-jit-dumps.md#disassembly-output

enable this on your computer and run dotnet in commandline to test JIT and GC outputs

DOTNET_JitDisasm={method-list} - output disassembly for the specified functions. E.g., DOTNET_JitDisasm=Main, DOTNET_JitDisasm=Main Test1 Test2, DOTNET_JitDisasm=* (for all functions).


/// <summary>
/// Converts a byte array to hex <see cref="string"/>.
/// </summary>
/// <param name="value">The byte array to convert.</param>
/// <returns>The converted hex <see cref="string"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToHexString(this byte[] value)
{
StringBuilder sb = new();
foreach (var b in value)
sb.AppendFormat("{0:x2}", b);
return sb.ToString();
#if NET9_0_OR_GREATER
return Convert.ToHexStringLower(value);
#else
return string.Create(value.Length * 2, value, (span, bytes) =>
{
for (var i = 0; i < bytes.Length; i++)
{
var b = bytes[i];
span[i * 2] = s_hexChars[b >> 4];
span[i * 2 + 1] = s_hexChars[b & 0xF];
}
});
#endif
}

/// <summary>
Expand All @@ -35,25 +48,44 @@ public static string ToHexString(this byte[] value)
/// <param name="value">The byte array to convert.</param>
/// <param name="reverse">Indicates whether it should be converted in the reversed byte order.</param>
/// <returns>The converted hex <see cref="string"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToHexString(this byte[] value, bool reverse = false)
{
StringBuilder sb = new();
for (var i = 0; i < value.Length; i++)
sb.AppendFormat("{0:x2}", value[reverse ? value.Length - i - 1 : i]);
return sb.ToString();
if (!reverse)
return ToHexString(value);

return string.Create(value.Length * 2, value, (span, bytes) =>
{
for (var i = 0; i < bytes.Length; i++)
{
var b = bytes[bytes.Length - i - 1];
span[i * 2] = s_hexChars[b >> 4];
span[i * 2 + 1] = s_hexChars[b & 0xF];
}
});
}

/// <summary>
/// Converts a byte array to hex <see cref="string"/>.
/// </summary>
/// <param name="value">The byte array to convert.</param>
/// <returns>The converted hex <see cref="string"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ToHexString(this ReadOnlySpan<byte> value)
{
StringBuilder sb = new();
foreach (var b in value)
sb.AppendFormat("{0:x2}", b);
#if NET9_0_OR_GREATER
return Convert.ToHexStringLower(value);
#else
// string.Create with ReadOnlySpan<char> not supported in NET5 or lower
var sb = new StringBuilder(value.Length * 2);
for (var i = 0; i < value.Length; i++)
{
var b = value[i];
sb.Append(s_hexChars[b >> 4]);
sb.Append(s_hexChars[b & 0xF]);
}
return sb.ToString();
#endif
}
}
}
Loading