HexMate icon indicating copy to clipboard operation
HexMate copied to clipboard

ToHex / FromHex in Ethereum

Open tkstanczak opened this issue 5 years ago • 3 comments

We use a lot of FromHex / ToHex and we have two switches that we need to use:

  1. Include 0x that pushes 0x characters in front
  2. remove leading zeros (thyat removes all leading zeros at the end

the second is problematic because it may lead to a change of length and oddity

I am running benchmarks of your method now against our fastest implementation

tkstanczak avatar Dec 05 '19 16:12 tkstanczak

        private struct StateSmall
        {
            public StateSmall(byte[] bytes, bool withZeroX)
            {
                Bytes = bytes;
                WithZeroX = withZeroX;
            }
            
            public byte[] Bytes;
            public bool WithZeroX;
        }
        private static uint[] Lookup32 = CreateLookup32("x2");

        private static uint[] CreateLookup32(string format)
        {
            uint[] result = new uint[256];
            for (int i = 0; i < 256; i++)
            {
                string s = i.ToString(format);
                result[i] = s[0] + ((uint) s[1] << 16);
            }

            return result;
        }
  [DebuggerStepThrough]
     public static string ByteArrayToHexViaLookup32Safe(byte[] bytes, bool withZeroX)
     {
         if (bytes.Length == 0)
         {
             return withZeroX ? "0x" : "";
         }
         
         int length = bytes.Length * 2 + (withZeroX ? 2 : 0);
         StateSmall stateToPass = new StateSmall(bytes, withZeroX);
         return string.Create(length, stateToPass, (chars, state) =>
         {
             int offset0x = 0;
             if (state.WithZeroX)
             {
                 chars[0] = '0';
                 chars[1] = 'x';
                 offset0x += 2;
             }

             Span<uint> charsAsInts = MemoryMarshal.Cast<char, uint>(chars.Slice(offset0x));
             int targetLength = state.Bytes.Length;
             for (int i = 0; i < targetLength; i += 1)
             {
                 uint val = Lookup32[state.Bytes[i]];
                 charsAsInts[i] = val;
             }
         });
     }
    [MemoryDiagnoser]
    [CoreJob(baseline: true)]
    public class ByteArrayToHexBenchmarks
    {
        private byte[] array = Bytes.FromHexString("0123456789abcdef");
        
        [GlobalSetup]
        public void Setup()
        {

        }

        [Benchmark(Baseline = true)]
        public string Current()
        {
            return array.ToHexString(true);
        }
        
        [Benchmark]
        public string Improved()
        {
            return Bytes.ByteArrayToHexViaLookup32Safe(array, true);
        }
        
        [Benchmark]
        public string HexMateA()
        {
            return Convert.ToHexString(array, HexFormattingOptions.Lowercase);
        }
    }
Method Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Current 53.14 ns 0.8076 ns 0.6744 ns 1.00 0.00 0.0136 - - 64 B
Improved 18.87 ns 0.4436 ns 0.6072 ns 0.35 0.01 0.0136 - - 64 B
HexMateA 35.21 ns 0.7732 ns 1.2704 ns 0.66 0.02 0.0119 - - 56 B

tkstanczak avatar Dec 05 '19 16:12 tkstanczak

I need to run it with big inputs (up to L2)

tkstanczak avatar Dec 05 '19 19:12 tkstanczak

Yup, HexMate is faster for anything longer, interestingly we call it a lot but 99% of times have arrays of 32 bytes there.

tkstanczak avatar Dec 05 '19 21:12 tkstanczak