5

有人知道是否有 .NET Framework 等效于交换 uint 中的字节顺序?

我正在尝试将一些使用 MSFT _byteswap_ulong(在 *Nix 世界中相当于 Apples OSSwapInt32 或 Bswap32)的自定义 c 哈希代码移植到 c#。我可以手动编写这个函数,但我怀疑它会利用任何编译器优化(例如,c/c++ 编译器提供的内在函数很难超越我希望运行时对内置功能也能做到这一点)。如果重要的话,我不在乎保留字节顺序。

我尝试了一个基于通用的解决方案,但我不相信这会是最佳的。

BitConverter.ToUInt32(BitConverter.GetBytes(g).Reverse().ToArray<byte>(),0);

编辑:

所以我计算出这个特定函数在十分钟内平均被调用了多少次(对于我们的一个哈希消费者)。这个函数被调用了 10,000,000,000 次。所以我设置了一些微分析来查看 c 代码与下面提供的解决方案(以及上面提出的解决方案)的性能。

C 代码在大约 10 分钟内运行这么多操作(使用内在函数)。在我值得信赖的笔记本电脑上运行 1500 毫秒。我在上面展示的 c# 代码运行了将近 2,689,581 毫秒。巨大的差异。Matthew Watson 提供的 c# 代码运行时间将近 36000 毫秒。Caramiriel 提出的第一个解决方案的运行时间几乎为 115,014 毫秒,而第二个解决方案的运行时间几乎为 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"));
        }
    }
}
4

4 回答 4

7

从 .NET Core 2.1 开始,BinaryPrimitives.ReverseEndianness 为该功能提供了优化的软件实现。从 .NET Core 3.0 开始,它使用 JIT 内部函数实现,该内部函数被编译成使用 bswap 指令的非常高效的机器代码。

于 2019-05-17T19:49:46.577 回答
6

我不认为你会像 _byteswap_ulong 一样快地得到任何东西,但如果你使用这个:

public static uint SwapBytes(uint word)
{
    return ((word>>24)&0x000000FF) | ((word>>8)&0x0000FF00) | ((word<<8)&0x00FF0000) | ((word<<24)&0xFF000000);            
}

至少 JIT 优化器可能会内联它。

于 2013-06-22T15:08:10.240 回答
3

一种方法是直接在 (U)Int32 类型上工作。这为您提供了最少的开销,例如在 Linq 中使用的状态机和所涉及的方法调用。

unchecked {     
    return 
            ((value & 0xff000000) >> 24) |
            ((value & 0x00ff0000) >> 8) |
            ((value & 0x0000ff00) << 8) |
            ((value & 0x000000ff) << 24);
}

这将每个字节在整数内的相应位置分开,并将其移动(移位)到正确的位置。最后,移动的字节被拼接在一起(OR -ed)。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;
}
于 2013-06-22T15:08:20.203 回答
0

您可以使用IPAddress.NetworkToHostOrder方法,它对 int16 int32 int64 最有效,实际上使用单个 CPU 指令,大多数情况下会内联。

Big-endian 系统上的 bot 这种方法是无操作的,但我不知道任何运行当前 dotnet 的 BE 系统。

或者您可以使用@scott 的答案和 BinaryPrimitives.ReverseEndianness 但这仅适用于网络核心。

于 2020-08-18T12:13:08.410 回答