.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等效于_byteswap_ulong/OSSwapInt32/或bswap32

从.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,但这只适用于网络核心。