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
版本快得多。我写了另一个测试脚本,我注意到 bitwise
和 unsafe
提供几乎相同的性能。
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();
}
}
第一次编辑:
我尝试使用 fixed
和 stackalloc
来实现,与 bitwise
进行比较。在我的测试代码中,按位似乎比 unsafe
方法快。
测量结果如下:
- 循环
10000000
次后秒表频率 3515622
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, long
和 ushort, uint, ulong
转换为 byte array
的按位方式感到困惑,反之亦然.
性能对我来说真的很重要。
我知道使用 BitConverter
和 Array.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" 情况下使用偏移或掩码。
用我最新的脚本实现和测试,你仍然可以进一步调整它,但Bitwise
和Unsafe
之间的性能差异非常小。
环境:
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;
}
第二次编辑:
我认为我的原始测试脚本有问题,10000000
次循环实际上是在处理数组的相同内存位置,这使得 unsafe
版本(由Marc here) 比 bitwise
版本快得多。我写了另一个测试脚本,我注意到 bitwise
和 unsafe
提供几乎相同的性能。
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();
}
}
第一次编辑:
我尝试使用 fixed
和 stackalloc
来实现,与 bitwise
进行比较。在我的测试代码中,按位似乎比 unsafe
方法快。
测量结果如下:
- 循环
10000000
次后秒表频率3515622
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, long
和 ushort, uint, ulong
转换为 byte array
的按位方式感到困惑,反之亦然.
性能对我来说真的很重要。
我知道使用 BitConverter
和 Array.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" 情况下使用偏移或掩码。
用我最新的脚本实现和测试,你仍然可以进一步调整它,但Bitwise
和Unsafe
之间的性能差异非常小。
环境:
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;
}