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

Base58 perfomance patch #3186

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
40 changes: 31 additions & 9 deletions src/Neo/Cryptography/Base58.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
// modifications are permitted.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using static Neo.Helper;

namespace Neo.Cryptography
{
Expand All @@ -27,6 +28,9 @@ public static class Base58
/// </summary>
public const string Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

private static readonly char s_zeroChar = Alphabet[0];
private static readonly Lazy<IReadOnlyDictionary<char, int>> s_alphabetDic = new(() => Enumerable.Range(0, Alphabet.Length).ToDictionary(t => Alphabet[t], t => t));
Copy link
Member

Choose a reason for hiding this comment

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

This cost more in the current code in resources of memory management for the GC in dotnet.


/// <summary>
/// Converts the specified <see cref="string"/>, which encodes binary data as base-58 digits, to an equivalent byte array. The encoded <see cref="string"/> contains the checksum of the binary data.
/// </summary>
Expand Down Expand Up @@ -74,19 +78,27 @@ public static byte[] Decode(string input)
var bi = BigInteger.Zero;
for (int i = 0; i < input.Length; i++)
{
int digit = Alphabet.IndexOf(input[i]);
if (digit < 0)
if (!s_alphabetDic.Value.TryGetValue(input[i], out var digit))
shargon marked this conversation as resolved.
Show resolved Hide resolved
throw new FormatException($"Invalid Base58 character '{input[i]}' at position {i}");
bi = bi * Alphabet.Length + digit;
}

// Encode BigInteger to byte[]
// Leading zero bytes get encoded as leading `1` characters
int leadingZeroCount = input.TakeWhile(c => c == Alphabet[0]).Count();
var leadingZeros = new byte[leadingZeroCount];
if (bi.IsZero) return leadingZeros;
var bytesWithoutLeadingZeros = bi.ToByteArray(isUnsigned: true, isBigEndian: true);
return Concat(leadingZeros, bytesWithoutLeadingZeros);
int leadingZeroCount = LeadingBase58Zeros(input);
if (bi.IsZero)
{
return new byte[leadingZeroCount];
}

int decodedSize = bi.GetByteCount(true) + leadingZeroCount;
shargon marked this conversation as resolved.
Show resolved Hide resolved

Span<byte> result = decodedSize <= 128
? stackalloc byte[decodedSize]
: new byte[decodedSize];

_ = bi.TryWriteBytes(result[leadingZeroCount..], out _, true, true);
shargon marked this conversation as resolved.
Show resolved Hide resolved
return result.ToArray();
}

/// <summary>
Expand All @@ -111,9 +123,19 @@ public static string Encode(ReadOnlySpan<byte> input)
// Append `1` for each leading 0 byte
for (int i = 0; i < input.Length && input[i] == 0; i++)
{
sb.Insert(0, Alphabet[0]);
sb.Insert(0, s_zeroChar);
}
return sb.ToString();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int LeadingBase58Zeros(string collection)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
var i = 0;
var len = collection.Length;
for (; i < len && collection[i] == s_zeroChar; i++) { }

return i;
}
}
}
Loading