有没有人知道是否有一个.NET Framework等价物来交换uint中的字节顺序?
我正在尝试将一些使用MSFT _byteswap_ulong(相当于App Nsix中的Apples OSSwapInt32或Bswap32)的自定义c哈希代码移植到c#。我可以手动编写这个函数,但我怀疑它会利用任何编译器优化(例如c / c ++编译器提供难以超越的内在函数我希望运行时与内置函数相同)。如果重要,我不关心保留字节序。
我尝试过基于通用的解决方案,但我不相信这会是最佳的。
BitConverter.ToUInt32(BitConverter.GetBytes(g).Reverse().ToArray<byte>(),0);
编辑:
所以我想出了这个特定函数平均在十分钟内被调用的次数(对于我们的一个哈希消费者)。此函数被调用10000000000次。因此,我设置了一些微观分析,以查看c代码与下面提供的解决方案(以及上面提出的解决方案)的性能。
C代码运行许多操作(使用内在函数)。在我可靠的笔记本电脑上1500毫秒。 我上面提到的c#代码大约运行2689581 ms。一个巨大的差异。 Matthew Watson提供的c#代码运行了将近36000毫秒。 Caramiriel提出的第一个解决方案运行时间差不多是115014毫秒,第二个解决方案提供的差不多是36000个。
虽然这些解决方案都没有接近内在调用的速度,但它们比我原来的解决方案要好得多(对于那么多计算,从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"));
}
}
}
答案 0 :(得分:6)
我认为你没有像_byteswap_ulong那样快得到任何东西,但如果你使用它:
public static uint SwapBytes(uint word)
{
return ((word>>24)&0x000000FF) | ((word>>8)&0x0000FF00) | ((word<<8)&0x00FF0000) | ((word<<24)&0xFF000000);
}
至少JIT优化器可能会内联它。
答案 1 :(得分:3)
一种方法是直接在(U)Int32型上工作。这为您提供了最少的开销,例如Linq中使用的状态机以及所涉及的方法调用。
unchecked {
return
((value & 0xff000000) >> 24) |
((value & 0x00ff0000) >> 8) |
((value & 0x0000ff00) << 8) |
((value & 0x000000ff) << 24);
}
这将每个字节分隔在整数中的相应位置,并将其移动(shifting)到正确的位置。最后,将移动的字节拼接在一起(OR - ed)。 Unchecked
只是抑制可能导致过度/下溢的异常,这些异常现在不相关(因为不涉及检查而保存了性能)。
或者C union方式,虽然更具可读性,但在我的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;
}
答案 2 :(得分:0)
从.NET Core 2.1开始,BinaryPrimitives.ReverseEndianness为此功能提供了优化的软件实现。从.NET Core 3.0开始,它使用JIT内在函数实现,该内在函数使用bswap指令插入非常高效的机器代码中。
答案 3 :(得分:0)
您可以使用IPAddress.NetworkToHostOrder方法,它将对int16 int32 int64最为有效,实际上使用的是单CPU指令,该指令通常会内联。
在Big-endian系统上,此方法将无效,但我不知道任何运行当前dotnet的BE系统。
或者您可以使用@scott的答案和BinaryPrimitives.ReverseEndianness,但这仅用于网络核心。