C# Signed & Unsigned Integral to Big Endian Byte Array,反之亦然,使用具有 "best" 性能的按位方式

C# Signed & Unsigned Integral to Big Endian Byte Array, and vice versa using Bitwise way with "best" performance

第二次编辑:

我认为我的原始测试脚本有问题,10000000 次循环实际上是在处理数组的相同内存位置,这使得 unsafe 版本(由Marc here) 比 bitwise 版本快得多。我写了另一个测试脚本,我注意到 bitwiseunsafe 提供几乎相同的性能。

loop for 10,000,000 times

Bitwise: 4218484; UnsafeRaw: 4101719

0.0284673328426447545529081831 (~2% Difference)

代码如下:

unsafe class UnsafeRaw
{
    public static short ToInt16BigEndian(byte* buf)
    {
        return (short)ToUInt16BigEndian(buf);
    }

    public static int ToInt32BigEndian(byte* buf)
    {
        return (int)ToUInt32BigEndian(buf);
    }

    public static long ToInt64BigEndian(byte* buf)
    {
        return (long)ToUInt64BigEndian(buf);
    }

    public static ushort ToUInt16BigEndian(byte* buf)
    {
        return (ushort)((*buf++ << 8) | *buf);
    }

    public static uint ToUInt32BigEndian(byte* buf)
    {
        return unchecked((uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf));
    }

    public static ulong ToUInt64BigEndian(byte* buf)
    {
        unchecked
        {
            var x = (uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf++);
            var y = (uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf);
            return ((ulong)x << 32) | y;
        }
    }
}

class Bitwise
{
    public static short ToInt16BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked((short)(buffer[beginIndex] << 8 | buffer[beginIndex + 1]));
    }

    public static int ToInt32BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked(buffer[beginIndex] << 24 | buffer[beginIndex + 1] << 16 | buffer[beginIndex + 2] << 8 | buffer[beginIndex + 3]);
    }

    public static long ToInt64BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked((long)buffer[beginIndex] << 56 | (long)buffer[beginIndex + 1] << 48 | (long)buffer[beginIndex + 2] << 40 | (long)buffer[beginIndex + 3] << 32 | (long)buffer[beginIndex + 4] << 24 | (long)buffer[beginIndex + 5] << 16 | (long)buffer[beginIndex + 6] << 8 | buffer[beginIndex + 7]);
    }

    public static ushort ToUInt16BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked((ushort)ToInt16BigEndian(buffer, beginIndex));
    }

    public static uint ToUInt32BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked((uint)ToInt32BigEndian(buffer, beginIndex));
    }

    public static ulong ToUInt64BigEndian(byte[] buffer, int beginIndex)
    {
        return unchecked((ulong)ToInt64BigEndian(buffer, beginIndex));
    }
}

class BufferTest
{
    static long LongRandom(long min, long max, Random rand)
    {
        long result = rand.Next((Int32)(min >> 32), (Int32)(max >> 32));
        result = result << 32;
        result = result | (long)rand.Next((Int32)min, (Int32)max);
        return result;
    }

    public static void Main()
    {
        const int times = 10000000;
        const int index = 100;

        Random r = new Random();

        Stopwatch sw1 = new Stopwatch();

        Console.WriteLine($"loop for {times:##,###} times");

        Thread.Sleep(1000);

        for (int j = 0; j < times; j++)
        {
            short a = (short)r.Next(short.MinValue, short.MaxValue);
            int b = r.Next(int.MinValue, int.MaxValue);
            long c = LongRandom(int.MinValue, int.MaxValue, r);
            ushort d = (ushort)r.Next(ushort.MinValue, ushort.MaxValue);
            uint e = (uint)r.Next(int.MinValue, int.MaxValue);
            ulong f = (ulong)LongRandom(int.MinValue, int.MaxValue, r);

            var arr1 = BitConverter.GetBytes(a);
            var arr2 = BitConverter.GetBytes(b);
            var arr3 = BitConverter.GetBytes(c);
            var arr4 = BitConverter.GetBytes(d);
            var arr5 = BitConverter.GetBytes(e);
            var arr6 = BitConverter.GetBytes(f);

            Array.Reverse(arr1);
            Array.Reverse(arr2);
            Array.Reverse(arr3);
            Array.Reverse(arr4);
            Array.Reverse(arr5);
            Array.Reverse(arr6);

            var largerArr1 = new byte[1024];
            var largerArr2 = new byte[1024];
            var largerArr3 = new byte[1024];
            var largerArr4 = new byte[1024];
            var largerArr5 = new byte[1024];
            var largerArr6 = new byte[1024];

            Array.Copy(arr1, 0, largerArr1, index, arr1.Length);
            Array.Copy(arr2, 0, largerArr2, index, arr2.Length);
            Array.Copy(arr3, 0, largerArr3, index, arr3.Length);
            Array.Copy(arr4, 0, largerArr4, index, arr4.Length);
            Array.Copy(arr5, 0, largerArr5, index, arr5.Length);
            Array.Copy(arr6, 0, largerArr6, index, arr6.Length);

            sw1.Start();
            var n1 = Bitwise.ToInt16BigEndian(largerArr1, index);
            var n2 = Bitwise.ToInt32BigEndian(largerArr2, index);
            var n3 = Bitwise.ToInt64BigEndian(largerArr3, index);
            var n4 = Bitwise.ToUInt16BigEndian(largerArr4, index);
            var n5 = Bitwise.ToUInt32BigEndian(largerArr5, index);
            var n6 = Bitwise.ToUInt64BigEndian(largerArr6, index);
            sw1.Stop();

            //Console.WriteLine(n1 == a);
            //Console.WriteLine(n2 == b);
            //Console.WriteLine(n3 == c);
            //Console.WriteLine(n4 == d);
            //Console.WriteLine(n5 == e);
            //Console.WriteLine(n6 == f);
        }

        Stopwatch sw2 = new Stopwatch();

        for (int j = 0; j < times; j++)
        {
            short a = (short)r.Next(short.MinValue, short.MaxValue);
            int b = r.Next(int.MinValue, int.MaxValue);
            long c = LongRandom(int.MinValue, int.MaxValue, r);
            ushort d = (ushort)r.Next(ushort.MinValue, ushort.MaxValue);
            uint e = (uint)r.Next(int.MinValue, int.MaxValue);
            ulong f = (ulong)LongRandom(int.MinValue, int.MaxValue, r);

            var arr1 = BitConverter.GetBytes(a);
            var arr2 = BitConverter.GetBytes(b);
            var arr3 = BitConverter.GetBytes(c);
            var arr4 = BitConverter.GetBytes(d);
            var arr5 = BitConverter.GetBytes(e);
            var arr6 = BitConverter.GetBytes(f);

            Array.Reverse(arr1);
            Array.Reverse(arr2);
            Array.Reverse(arr3);
            Array.Reverse(arr4);
            Array.Reverse(arr5);
            Array.Reverse(arr6);

            var largerArr1 = new byte[1024];
            var largerArr2 = new byte[1024];
            var largerArr3 = new byte[1024];
            var largerArr4 = new byte[1024];
            var largerArr5 = new byte[1024];
            var largerArr6 = new byte[1024];

            Array.Copy(arr1, 0, largerArr1, index, arr1.Length);
            Array.Copy(arr2, 0, largerArr2, index, arr2.Length);
            Array.Copy(arr3, 0, largerArr3, index, arr3.Length);
            Array.Copy(arr4, 0, largerArr4, index, arr4.Length);
            Array.Copy(arr5, 0, largerArr5, index, arr5.Length);
            Array.Copy(arr6, 0, largerArr6, index, arr6.Length);

            sw2.Start();
            unsafe
            {
                fixed (byte* p1 = &largerArr1[index], p2 = &largerArr2[index], p3 = &largerArr3[index], p4 = &largerArr4[index], p5 = &largerArr5[index], p6 = &largerArr6[index])
                {
                    var u1 = UnsafeRaw.ToInt16BigEndian(p1);
                    var u2 = UnsafeRaw.ToInt32BigEndian(p2);
                    var u3 = UnsafeRaw.ToInt64BigEndian(p3);
                    var u4 = UnsafeRaw.ToUInt16BigEndian(p4);
                    var u5 = UnsafeRaw.ToUInt32BigEndian(p5);
                    var u6 = UnsafeRaw.ToUInt64BigEndian(p6);

                    //Console.WriteLine(u1 == a);
                    //Console.WriteLine(u2 == b);
                    //Console.WriteLine(u3 == c);
                    //Console.WriteLine(u4 == d);
                    //Console.WriteLine(u5 == e);
                    //Console.WriteLine(u6 == f);
                }
            }
            sw2.Stop();
        }

        Console.WriteLine($"Bitwise: {sw1.ElapsedTicks}; UnsafeRaw: {sw2.ElapsedTicks}");

        Console.WriteLine((decimal)sw1.ElapsedTicks / sw2.ElapsedTicks - 1);

        Console.ReadKey();
    }
}

第一次编辑:

我尝试使用 fixedstackalloc 来实现,与 bitwise 进行比较。在我的测试代码中,按位似乎比 unsafe 方法快。

测量结果如下:

unsafe fixed - 2239790 ticks

bitwise - 672159 ticks

unsafe stackalloc - 1624166 ticks

我做错了什么吗?我以为 unsafe 会比 bitwise 快。

代码如下:

class Bitwise
{
    public static short ToInt16BigEndian(byte[] buf, int i)
    {
        return (short)((buf[i] << 8) | buf[i + 1]);
    }

    public static int ToInt32BigEndian(byte[] buf, int i)
    {
        return (buf[i] << 24) | (buf[i + 1] << 16) | (buf[i + 2] << 8) | buf[i + 3];
    }

    public static long ToInt64BigEndian(byte[] buf, int i)
    {
        return (buf[i] << 56) | (buf[i + 1] << 48) | (buf[i + 2] << 40) | buf[i + 3] << 32 | (buf[i + 4] << 24) | (buf[i + 5] << 16) | (buf[i + 6] << 8) | buf[i + 7];
    }

    public static ushort ToUInt16BigEndian(byte[] buf, int i)
    {
        ushort value = 0;

        for (var j = 0; j < 2; j++)
        {
            value = (ushort)unchecked((value << 8) | buf[j + i]);
        }

        return value;
    }

    public static uint ToUInt32BigEndian(byte[] buf, int i)
    {
        uint value = 0;

        for (var j = 0; j < 4; j++)
        {
            value = unchecked((value << 8) | buf[j + i]);
        }

        return value;
    }

    public static ulong ToUInt64BigEndian(byte[] buf, int i)
    {
        ulong value = 0;

        for (var j = 0; j < 8; j++)
        {
            value = unchecked((value << 8) | buf[i + j]);
        }

        return value;
    }
}

class Unsafe
{
    public static short ToInt16BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[2];

        arr[0] = buf[i + 1];
        arr[1] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(short*)ptr;
            }
        }
    }

    public static int ToInt32BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[4];

        arr[0] = buf[i + 3];
        arr[1] = buf[i + 2];
        arr[2] = buf[i + 1];
        arr[3] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(int*)ptr;
            }
        }
    }

    public static long ToInt64BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[8];

        arr[0] = buf[i + 7];
        arr[1] = buf[i + 6];
        arr[2] = buf[i + 5];
        arr[3] = buf[i + 6];
        arr[4] = buf[i + 3];
        arr[5] = buf[i + 2];
        arr[6] = buf[i + 1];
        arr[7] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(long*)ptr;
            }
        }
    }

    public static ushort ToUInt16BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[2];

        arr[0] = buf[i + 1];
        arr[1] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(ushort*)ptr;
            }
        }
    }

    public static uint ToUInt32BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[4];

        arr[0] = buf[i + 3];
        arr[1] = buf[i + 2];
        arr[2] = buf[i + 1];
        arr[3] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(uint*)ptr;
            }
        }
    }

    public static ulong ToUInt64BigEndian(byte[] buf, int i)
    {
        byte[] arr = new byte[8];

        arr[0] = buf[i + 7];
        arr[1] = buf[i + 6];
        arr[2] = buf[i + 5];
        arr[3] = buf[i + 6];
        arr[4] = buf[i + 3];
        arr[5] = buf[i + 2];
        arr[6] = buf[i + 1];
        arr[7] = buf[i];

        unsafe
        {
            fixed (byte* ptr = arr)
            {
                return *(ulong*)ptr;
            }
        }
    }
}

class UnsafeAlloc
{
    public static short ToInt16BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(short);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(short*)arr;
        }
    }

    public static int ToInt32BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(int);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(int*)arr;
        }
    }

    public static long ToInt64BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(long);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(long*)arr;
        }
    }

    public static ushort ToUInt16BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(ushort);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(ushort*)arr;
        }
    }

    public static uint ToUInt32BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(uint);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(uint*)arr;
        }
    }

    public static ulong ToUInt64BigEndian(byte[] buf, int i)
    {
        unsafe
        {
            const int length = sizeof(ulong);
            byte* arr = stackalloc byte[length];
            byte* p = arr;

            for (int j = length - 1; j >= 0; j--)
            {
                *p = buf[i + j];
                p++;
            }

            return *(ulong*)arr;
        }
    }
}

class Program
{
    static void Main()
    {
        short a = short.MinValue + short.MaxValue / 2;
        int b = int.MinValue + int.MaxValue / 2;
        long c = long.MinValue + long.MaxValue / 2;
        ushort d = ushort.MaxValue / 2;
        uint e = uint.MaxValue / 2;
        ulong f = ulong.MaxValue / 2;

        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine(d);
        Console.WriteLine(e);
        Console.WriteLine(f);
        Console.WriteLine();

        var arr1 = BitConverter.GetBytes(a);
        var arr2 = BitConverter.GetBytes(b);
        var arr3 = BitConverter.GetBytes(c);
        var arr4 = BitConverter.GetBytes(d);
        var arr5 = BitConverter.GetBytes(e);
        var arr6 = BitConverter.GetBytes(f);

        Array.Reverse(arr1);
        Array.Reverse(arr2);
        Array.Reverse(arr3);
        Array.Reverse(arr4);
        Array.Reverse(arr5);
        Array.Reverse(arr6);

        Console.WriteLine(Unsafe.ToInt16BigEndian(arr1, 0));
        Console.WriteLine(Unsafe.ToInt32BigEndian(arr2, 0));
        Console.WriteLine(Unsafe.ToInt64BigEndian(arr3, 0));
        Console.WriteLine(Unsafe.ToUInt16BigEndian(arr4, 0));
        Console.WriteLine(Unsafe.ToUInt32BigEndian(arr5, 0));
        Console.WriteLine(Unsafe.ToUInt64BigEndian(arr6, 0));
        Console.WriteLine();

        Console.WriteLine(Bitwise.ToInt16BigEndian(arr1, 0));
        Console.WriteLine(Bitwise.ToInt32BigEndian(arr2, 0));
        Console.WriteLine(Bitwise.ToInt64BigEndian(arr3, 0));
        Console.WriteLine(Bitwise.ToUInt16BigEndian(arr4, 0));
        Console.WriteLine(Bitwise.ToUInt32BigEndian(arr5, 0));
        Console.WriteLine(Bitwise.ToUInt64BigEndian(arr6, 0));
        Console.WriteLine();

        Console.WriteLine(UnsafeAlloc.ToInt16BigEndian(arr1, 0));
        Console.WriteLine(UnsafeAlloc.ToInt32BigEndian(arr2, 0));
        Console.WriteLine(UnsafeAlloc.ToInt64BigEndian(arr3, 0));
        Console.WriteLine(UnsafeAlloc.ToUInt16BigEndian(arr4, 0));
        Console.WriteLine(UnsafeAlloc.ToUInt32BigEndian(arr5, 0));
        Console.WriteLine(UnsafeAlloc.ToUInt64BigEndian(arr6, 0));
        Console.WriteLine();

        Stopwatch sw = new Stopwatch();
        sw.Start();

        int times = 10000000;

        var t0 = sw.ElapsedTicks;
        for (int i = 0; i < times; i++)
        {
            Unsafe.ToInt16BigEndian(arr1, 0);
            Unsafe.ToInt32BigEndian(arr2, 0);
            Unsafe.ToInt64BigEndian(arr3, 0);
            Unsafe.ToUInt16BigEndian(arr4, 0);
            Unsafe.ToUInt32BigEndian(arr5, 0);
            Unsafe.ToUInt64BigEndian(arr6, 0);
        }
        var t1 = sw.ElapsedTicks;

        var t2 = sw.ElapsedTicks;
        for (int i = 0; i < times; i++)
        {
            Bitwise.ToInt16BigEndian(arr1, 0);
            Bitwise.ToInt32BigEndian(arr2, 0);
            Bitwise.ToInt64BigEndian(arr3, 0);
            Bitwise.ToUInt16BigEndian(arr4, 0);
            Bitwise.ToUInt32BigEndian(arr5, 0);
            Bitwise.ToUInt64BigEndian(arr6, 0);
        }
        var t3 = sw.ElapsedTicks;

        var t4 = sw.ElapsedTicks;
        for (int i = 0; i < times; i++)
        {
            UnsafeAlloc.ToInt16BigEndian(arr1, 0);
            UnsafeAlloc.ToInt32BigEndian(arr2, 0);
            UnsafeAlloc.ToInt64BigEndian(arr3, 0);
            UnsafeAlloc.ToUInt16BigEndian(arr4, 0);
            UnsafeAlloc.ToUInt32BigEndian(arr5, 0);
            UnsafeAlloc.ToUInt64BigEndian(arr6, 0);
        }
        var t5 = sw.ElapsedTicks;

        Console.WriteLine($"{t1 - t0} {t3 - t2} {t5 - t4}");

        Console.ReadKey();
    }

    public static string ByteArrayToString(byte[] ba)
    {
        return string.Concat(ba.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')));
    }
}

原文:

我对将 c# 数据类型 short, int, longushort, uint, ulong 转换为 byte array 的按位方式感到困惑,反之亦然.

性能对我来说真的很重要。

我知道使用 BitConverterArray.Reverse 有一种 的方法来完成所有这些,但性能很糟糕。

我知道除了BitConverter,基本上还有两种方法,一种是bitwise,另一种是unsafe

在对 Whosebug 进行研究后,喜欢:

Efficient way to read big-endian data in C#

Bitwise endian swap for various types

In C#, convert ulong[64] to byte[512] faster?

Fast string to byte[] conversion

我先试了试bitwise方法,把这些小块拼成一个整体

15555
43425534
54354444354
432
234234
34324432234

15555
43425534
-1480130482 // wrong
432
234234
34324432234

我对所有这些移位更加困惑,这是我的测试代码:

class Program
{
    static void Main()
    {
        short a = 15555;
        int b = 43425534;
        long c = 54354444354;
        ushort d = 432;
        uint e = 234234;
        ulong f = 34324432234;

        Console.WriteLine(a);
        Console.WriteLine(b);
        Console.WriteLine(c);
        Console.WriteLine(d);
        Console.WriteLine(e);
        Console.WriteLine(f);

        var arr1 = BitConverter.GetBytes(a);
        var arr2 = BitConverter.GetBytes(b);
        var arr3 = BitConverter.GetBytes(c);
        var arr4 = BitConverter.GetBytes(d);
        var arr5 = BitConverter.GetBytes(e);
        var arr6 = BitConverter.GetBytes(f);

        //Console.WriteLine(ByteArrayToString(arr1));
        //Console.WriteLine(ByteArrayToString(arr2));
        //Console.WriteLine(ByteArrayToString(arr3));
        //Console.WriteLine(ByteArrayToString(arr4));
        //Console.WriteLine(ByteArrayToString(arr5));
        //Console.WriteLine(ByteArrayToString(arr6));

        Array.Reverse(arr1);
        Array.Reverse(arr2);
        Array.Reverse(arr3);
        Array.Reverse(arr4);
        Array.Reverse(arr5);
        Array.Reverse(arr6);

        //Console.WriteLine(ByteArrayToString(arr1));
        //Console.WriteLine(ByteArrayToString(arr2));
        //Console.WriteLine(ByteArrayToString(arr3));
        //Console.WriteLine(ByteArrayToString(arr4));
        //Console.WriteLine(ByteArrayToString(arr5));
        //Console.WriteLine(ByteArrayToString(arr6));

        Console.WriteLine(ToInt16BigEndian(arr1, 0));
        Console.WriteLine(ToInt32BigEndian(arr2, 0));
        Console.WriteLine(ToInt64BigEndian(arr3, 0));
        Console.WriteLine(ToUInt16BigEndian(arr4, 0));
        Console.WriteLine(ToUInt32BigEndian(arr5, 0));
        Console.WriteLine(ToUInt64BigEndian(arr6, 0));

        Console.ReadKey();
    }

    public static string ByteArrayToString(byte[] ba)
    {
        return string.Concat(ba.Select(b => Convert.ToString(b, 2).PadLeft(8, '0')));
    }

    public static short ToInt16BigEndian(byte[] buf, int i)
    {
        return (short)((buf[i] << 8) | buf[i + 1]);
    }

    public static int ToInt32BigEndian(byte[] buf, int i)
    {
        return (buf[i] << 24) | (buf[i + 1] << 16) | (buf[i + 2] << 8) | buf[i + 3];
    }

    public static long ToInt64BigEndian(byte[] buf, int i)
    {
        return (buf[i] << 56) | (buf[i + 1] << 48) | (buf[i + 2] << 40) | (buf[i + 3] << 32) | (buf[i + 4] << 24) | (buf[i + 5] << 16) | (buf[i + 6] << 8) | buf[i + 7];
    }

    public static ushort ToUInt16BigEndian(byte[] buf, int i)
    {
        ushort value = 0;

        for (var j = 0; j < 2; j++)
        {
            value = (ushort)unchecked((value << 8) | buf[j + i]);
        }

        return value;
    }

    public static uint ToUInt32BigEndian(byte[] buf, int i)
    {
        uint value = 0;

        for (var j = 0; j < 4; j++)
        {
            value = unchecked((value << 8) | buf[j + i]);
        }

        return value;
    }

    public static ulong ToUInt64BigEndian(byte[] buf, int i)
    {
        ulong value = 0;

        for (var j = 0; j < 8; j++)
        {
            value = unchecked((value << 8) | buf[i + j]);
        }

        return value;
    }
}

我要的解决方案提供最佳性能,代码风格一致和代码的清洁度

I'm asking a solution that provides best performance, with consistent code style and cleanness of the code.

没有;选择任意两个。你不能同时拥有这三个。例如,如果您追求 最佳 性能,那么您可能不得不在其他一些方面做出妥协。

将来,Span<T> (Span<byte>) 将对这种情况非常有用 - 多个 IO API 获得对 Span<T>Memory<T> 的支持 - 但 现在你最好的选择可能是unsafe代码使用stackalloc byte*(或在byte[]上使用fixed)并直接写入,在 "other endian" 情况下使用偏移或掩码。

用我最新的脚本实现和测试,你仍然可以进一步调整它,但BitwiseUnsafe之间的性能差异非常小。

环境:

Intel Core i7-7700 CPU @3.6GHz
8.00 GB
64-bit
Windows 10 Pro
.NET FRAMEWORK 4.5.1

这是我的结果:

Loop for 10 x 10,000,000 times

READ VALUE FROM BUFFER ANY CPU

Bitwise: 27270845; UnsafeRaw: 26828068;
UnsafeRaw is 1.65% faster than Bitwise

READ VALUE FROM BUFFER X64

Bitwise: 27210757; UnsafeRaw: 26847482;
UnsafeRaw is 1.35% faster than Bitwise

WRITE VALUE TO BUFFER ANY CPU

Bitwise: 26364519; UnsafeRaw: 26258470;
UnsafeRaw is 0.40% faster than Bitwise

WRITE VALUE TO BUFFER X64

Bitwise: 25728215; UnsafeRaw: 25733755;
UnsafeRaw is -0.02% faster than Bitwise

如果有人想做自己的测试,请在此处粘贴我的代码。

按位,从缓冲区读取值

public static short ToInt16BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked((short)(buffer[beginIndex] << 8 | buffer[beginIndex + 1]));
}

public static int ToInt32BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked(buffer[beginIndex] << 24 | buffer[beginIndex + 1] << 16 | buffer[beginIndex + 2] << 8 | buffer[beginIndex + 3]);
}

public static long ToInt64BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked((long)buffer[beginIndex] << 56 | (long)buffer[beginIndex + 1] << 48 | (long)buffer[beginIndex + 2] << 40 | (long)buffer[beginIndex + 3] << 32 | (long)buffer[beginIndex + 4] << 24 | (long)buffer[beginIndex + 5] << 16 | (long)buffer[beginIndex + 6] << 8 | buffer[beginIndex + 7]);
}

public static ushort ToUInt16BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked((ushort)ToInt16BigEndian(buffer, beginIndex));
}

public static uint ToUInt32BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked((uint)ToInt32BigEndian(buffer, beginIndex));
}

public static ulong ToUInt64BigEndian(this byte[] buffer, int beginIndex)
{
    return unchecked((ulong)ToInt64BigEndian(buffer, beginIndex));
}

按位,将值写入缓冲区

public static void SetBuffer(this ushort value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 8);
    arr[beginIndex + 1] = (byte)value;
}

public static void SetBuffer(this uint value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 24);
    arr[beginIndex + 1] = (byte)(value >> 16);
    arr[beginIndex + 2] = (byte)(value >> 8);
    arr[beginIndex + 3] = (byte)value;
}

public static void SetBuffer(this int value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 24);
    arr[beginIndex + 1] = (byte)(value >> 16);
    arr[beginIndex + 2] = (byte)(value >> 8);
    arr[beginIndex + 3] = (byte)value;
}

public static void SetBuffer(this short value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 8);
    arr[beginIndex + 1] = (byte)value;
}

public static void SetBuffer(this ulong value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 56);
    arr[beginIndex + 1] = (byte)(value >> 48);
    arr[beginIndex + 2] = (byte)(value >> 40);
    arr[beginIndex + 3] = (byte)(value >> 32);
    arr[beginIndex + 4] = (byte)(value >> 24);
    arr[beginIndex + 5] = (byte)(value >> 16);
    arr[beginIndex + 6] = (byte)(value >> 8);
    arr[beginIndex + 7] = (byte)value;
}

public static void SetBuffer(this long value, byte[] arr, int beginIndex)
{
    arr[beginIndex] = (byte)(value >> 56);
    arr[beginIndex + 1] = (byte)(value >> 48);
    arr[beginIndex + 2] = (byte)(value >> 40);
    arr[beginIndex + 3] = (byte)(value >> 32);
    arr[beginIndex + 4] = (byte)(value >> 24);
    arr[beginIndex + 5] = (byte)(value >> 16);
    arr[beginIndex + 6] = (byte)(value >> 8);
    arr[beginIndex + 7] = (byte)value;
}

不安全,从缓冲区读取值

public static short ToInt16BigEndian(byte* buf)
{
    return (short)ToUInt16BigEndian(buf);
}

public static int ToInt32BigEndian(byte* buf)
{
    return (int)ToUInt32BigEndian(buf);
}

public static long ToInt64BigEndian(byte* buf)
{
    return (long)ToUInt64BigEndian(buf);
}

public static ushort ToUInt16BigEndian(byte* buf)
{
    return (ushort)((*buf++ << 8) | *buf);
}

public static uint ToUInt32BigEndian(byte* buf)
{
    return unchecked((uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf));
}

public static ulong ToUInt64BigEndian(byte* buf)
{
    unchecked
    {
        var x = (uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf++);
        var y = (uint)((*buf++ << 24) | (*buf++ << 16) | (*buf++ << 8) | *buf);
        return ((ulong)x << 32) | y;
    }
}

不安全,将值写入缓冲区

public static void SetBuffer(byte* arr, ushort value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}

public static void SetBuffer(byte* arr, uint value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 24);
    *(arr + beginIndex++) = (byte)(value >> 16);
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}

public static void SetBuffer(byte* arr, ulong value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 56);
    *(arr + beginIndex++) = (byte)(value >> 48);
    *(arr + beginIndex++) = (byte)(value >> 40);
    *(arr + beginIndex++) = (byte)(value >> 32);
    *(arr + beginIndex++) = (byte)(value >> 24);
    *(arr + beginIndex++) = (byte)(value >> 16);
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}

public static void SetBuffer(byte* arr, short value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}

public static void SetBuffer(byte* arr, int value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 24);
    *(arr + beginIndex++) = (byte)(value >> 16);
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}

public static void SetBuffer(byte* arr, long value, int beginIndex)
{
    *(arr + beginIndex++) = (byte)(value >> 56);
    *(arr + beginIndex++) = (byte)(value >> 48);
    *(arr + beginIndex++) = (byte)(value >> 40);
    *(arr + beginIndex++) = (byte)(value >> 32);
    *(arr + beginIndex++) = (byte)(value >> 24);
    *(arr + beginIndex++) = (byte)(value >> 16);
    *(arr + beginIndex++) = (byte)(value >> 8);
    *(arr + beginIndex) = (byte)value;
}