.NET等效于_byteswap_ulong/OSSwapInt32/或bswap32
本文关键字:OSSwapInt32 bswap32 ulong byteswap NET | 更新日期: 2023-09-27 18:19:34
有人知道是否有.NET Framework等效程序可以交换uint中的字节顺序吗?
我正在尝试将一些使用MSFTs_byteswap_ulong(相当于*Nix世界中的Apples OSSwapInt32或Bswap32)的自定义c哈希代码移植到c#。我可以手动编写这个函数,但我怀疑它是否会利用任何编译器优化(例如,c/c++编译器提供的内部函数很难超越它。我希望运行时对内置功能也能做到这一点)。如果重要的话,我不在乎保留endianness。
我尝试过基于Generic的解决方案,但不相信这是最优的。
BitConverter.ToUInt32(BitConverter.GetBytes(g).Reverse().ToArray<byte>(),0);
编辑:
因此,我计算出了这个特定函数在平均十分钟内被调用的次数(对于我们的一个散列消费者)。此函数被调用10000000000次。因此,我设置了一些微评测,以查看c代码与下面提供的解决方案(以及上面提出的解决方案)的性能。
在我可靠的笔记本电脑上,C代码在大约1500毫秒内运行了这么多操作(使用内部)。我上面介绍的c#代码运行时间几乎为2689581毫秒。这是一个巨大的差异。Matthew Watson提出的c#代码运行时间将近36000毫秒。Caramiriel提出的第一个解决方案运行时间近115014 ms,提供的第二个解决方案的运行时间近36000 ms。
虽然这些解决方案都没有达到内在调用的速度,但它们比我的原始解决方案要好得多(计算次数从44分钟到36秒)。这对于我的申请来说是完全可以接受的。尽管如果.NET编译器提供一些与本机编译器相同的内在功能,那就太好了。
为了完整起见,这里是我的微基准标记的C代码:
#include "stdafx.h"
#include "windows.h"
unsigned long Swap(unsigned int value)
{
return _byteswap_uint64(value);
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned int value = 0x01020304;
unsigned long NUMITER = 10000000000;
unsigned long a=0;
unsigned long z=0;
int throwAwayLoopCount = 5;
for (int k = 0; k < throwAwayLoopCount; ++k)
{
a = GetTickCount();
for (unsigned long i = 0; i < NUMITER; ++i)
{
value = Swap(value);
}
z = GetTickCount();
printf("Baseline, Cached: time is %4lld milliseconds: value%4lld'n", z-a,value);
}
printf("Baseline, Cached: time is %4lld milliseconds'n", z-a);
return 0;
}
以下是用于对所提供的解决方案进行基准测试的c#代码:
namespace ByteSwapProfiler
{
using System.Runtime.InteropServices;
using System.Diagnostics;
[StructLayout(LayoutKind.Explicit)]
internal struct UInt32Union
{
[FieldOffset(0)]
public UInt32 Value;
[FieldOffset(0)]
public byte Byte1;
[FieldOffset(1)]
public byte Byte2;
[FieldOffset(2)]
public byte Byte3;
[FieldOffset(3)]
public byte Byte4;
}
class Program
{
static uint ByteSwapNaive(uint g)
{
return BitConverter.ToUInt32(BitConverter.GetBytes(g).Reverse().ToArray<byte>(), 0);
}
static uint ByteSwapCaramiriel1(uint value)
{
unchecked
{
return ((value & 0xff000000) >> 24) |
((value & 0x00ff0000) >> 8) |
((value & 0x0000ff00) << 8) |
((value & 0x000000ff) << 24);
}
}
static uint ByteSwapCaramiriel2(UInt32Union src)
{
UInt32Union dest = new UInt32Union
{
Byte1 = src.Byte4,
Byte2 = src.Byte3,
Byte3 = src.Byte2,
Byte4 = src.Byte1
};
return dest.Value;
}
static uint ByteSwapMatthewWatson(uint word)
{
return ((word >> 24) & 0x000000FF) | ((word >> 8) & 0x0000FF00) | ((word << 8) & 0x00FF0000) | ((word << 24) & 0xFF000000);
}
static void Main(string[] args)
{
uint value= 0x01020304;
UInt32Union src = new UInt32Union();
src.Value = value;
ulong NUMITER = 10000000000;
uint throwAwayLoopCount = 5;
var sw = new Stopwatch();
string name = "Naive";
//for (int k = 0; k < throwAwayLoopCount; ++k)
{
sw = Stopwatch.StartNew();
for (ulong i = 0; i < NUMITER; ++i)
{
value = ByteSwapNaive(value);
}
sw.Stop();
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds. Value:{2} 'n", name, (sw.ElapsedMilliseconds).ToString("0"),value);
}
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds.'n", name, (sw.ElapsedMilliseconds).ToString("0"));
name = "MatthewWatson";
for (int k = 0; k < throwAwayLoopCount; ++k)
{
sw = Stopwatch.StartNew();
for (ulong i = 0; i < NUMITER; ++i)
{
value = ByteSwapMatthewWatson(value);
}
sw.Stop();
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds. Value:{2} 'n", name, (sw.ElapsedMilliseconds).ToString("0"), value);
}
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds.'n", name, (sw.ElapsedMilliseconds).ToString("0"));
name = "Caramiriel2";
for (int k = 0; k < throwAwayLoopCount; ++k)
{
sw = Stopwatch.StartNew();
for (ulong i = 0; i < NUMITER; ++i)
{
value = ByteSwapCaramiriel2(src);
}
sw.Stop();
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds. Value:{2} 'n", name, (sw.ElapsedMilliseconds).ToString("0"), value);
}
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds.'n", name, (sw.ElapsedMilliseconds).ToString("0"));
name = "Caramiriel1";
for (int k = 0; k < throwAwayLoopCount; ++k)
{
sw = Stopwatch.StartNew();
for (ulong i = 0; i < NUMITER; ++i)
{
value = ByteSwapCaramiriel1(value);
}
sw.Stop();
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds. Value:{2} 'n", name, (sw.ElapsedMilliseconds).ToString("0"), value);
}
Console.Write("{0,-13}, Cached: time is {1,7} milliseconds.'n", name, (sw.ElapsedMilliseconds).ToString("0"));
}
}
}
从.NET Core 2.1开始,BinaryPrimities.ReverseEndianness为该功能提供了优化的软件实现。从.NET Core 3.0开始,它是用JIT内部实现的,JIT内部可以被转换为使用bswap指令的非常高效的机器代码。
我认为你不会得到像_byteswap_ulong这样快的东西,但如果你使用这个:
public static uint SwapBytes(uint word)
{
return ((word>>24)&0x000000FF) | ((word>>8)&0x0000FF00) | ((word<<8)&0x00FF0000) | ((word<<24)&0xFF000000);
}
至少JIT优化器可能会内联它。
一种方法是直接处理(U)Int32类型。这给了您最少的开销,例如Linq中使用的状态机和所涉及的方法调用。
unchecked {
return
((value & 0xff000000) >> 24) |
((value & 0x00ff0000) >> 8) |
((value & 0x0000ff00) << 8) |
((value & 0x000000ff) << 24);
}
这会将整数中相应位置的每个字节分开,并将其移动(移位)到正确的位置。最后,移动的字节被缝合(OR)在一起。Unchecked
只是抑制可能导致溢出/下溢的异常,而这些异常目前并不相关(由于不涉及检查,因此可以节省性能)。
或者C联合方式,虽然可读性更强,但在我的VM上速度慢了一倍:
[StructLayout(LayoutKind.Explicit)]
internal struct UInt32Union
{
[FieldOffset(0)] public UInt32 Value;
[FieldOffset(0)] public byte Byte1;
[FieldOffset(1)] public byte Byte2;
[FieldOffset(2)] public byte Byte3;
[FieldOffset(3)] public byte Byte4;
}
static UInt32 Swap( UInt32 value )
{
UInt32Union src = new UInt32Union
src.Value = value;
UInt32Union dest = new UInt32Union
{
Byte1 = src.Byte4,
Byte2 = src.Byte3,
Byte3 = src.Byte2,
Byte4 = src.Byte1
};
return dest.Value;
}
您可以使用IPAddress.NetworkToHostOrder方法,它对int16 int32 int64最有效,实际上使用的是大多数情况下都会内联的单CPU指令。
在Big endian系统上,这种方法不会有任何操作,但我不知道有任何be系统运行当前的dotnet。
或者你可以使用@scott的答案和BinaryPrimities.ReverseEndianness,但这只适用于网络核心。