改变字节顺序的最快方法

Posted

技术标签:

【中文标题】改变字节顺序的最快方法【英文标题】:Quickest way to change endianness 【发布时间】:2011-09-02 04:59:32 【问题描述】:

反转 16 位和 32 位整数的字节顺序的最快方法是什么。我通常会做类似的事情(这个编码是在 C++ 的 Visual Studio 中完成的):

union bytes4

    __int32 value;
    char ch[4];
;

union bytes2

    __int16 value;
    char ch[2];
;

__int16 changeEndianness16(__int16 val)

    bytes2 temp;
    temp.value=val;

    char x= temp.ch[0];
    temp.ch[0]=temp.ch[1];
    temp.ch[1]=x;
    return temp.value;


__int32 changeEndianness32(__int32 val)

    bytes4 temp;
    temp.value=val;
    char x;

    x= temp.ch[0];
    temp.ch[0]=temp.ch[1];
    temp.ch[1]=x;

    x= temp.ch[2];
    temp.ch[2]=temp.ch[3];
    temp.ch[3]=x;
    return temp.value;

有没有什么更快的方法来做同样的事情,我不必做这么多的计算?

【问题讨论】:

参见 [this topic][1],它提到使用 intrin.h。 [1]:***.com/questions/105252/… @Sebastiaan 该链接包含我想要的所有内容! 【参考方案1】:

您为什么不使用内置的swab 函数,它可能比您的代码优化得更好?

除此之外,通常的位移操作一开始应该很快,并且使用如此广泛,它们可能会被优化器识别并被更好的代码取代。


由于其他答案有严重的错误,我会发布一个更好的实现:

int16_t changeEndianness16(int16_t val)

    return (val << 8) |          // left-shift always fills with zeros
          ((val >> 8) & 0x00ff); // right-shift sign-extends, so force to zero

我测试的编译器都没有为此代码生成rolw,我认为稍长的序列(就指令数而言)实际上更快。基准测试会很有趣。

对于 32 位,有几种可能的操作顺序:

//version 1
int32_t changeEndianness32(int32_t val)

    return (val << 24) |
          ((val <<  8) & 0x00ff0000) |
          ((val >>  8) & 0x0000ff00) |
          ((val >> 24) & 0x000000ff);


//version 2, one less OR, but has data dependencies
int32_t changeEndianness32(int32_t val)

    int32_t tmp = (val << 16) |
                 ((val >> 16) & 0x00ffff);
    return ((tmp >> 8) & 0x00ff00ff) | ((tmp & 0x00ff00ff) << 8);

【讨论】:

@James:我输入了swab,因为我是认真的:msdn.microsoft.com/en-us/library/e8cxb8tk 通过转换为无符号类型,您可以完全避免符号扩展,因此您不需要位掩码。 (val &gt;&gt; 8) &amp; 0x00ff 得到 ((uint16_t)val) &gt;&gt; 8。此外,由于性能下降,我会将其放在定义或内联函数中。 @yourmt:您当然希望将这些内联,但添加额外的关键字并不能使答案更清晰。此外,您可以避免使用该位掩码,但不能避免其他位掩码。我认为一致性更清晰(编译器无论如何都应该做同样的事情)。 1. swab 不能单独解决 32 位的情况,尽管它可以正确处理 16 位的情况 2. 根据设计,人们期望 swab 会更慢,因为它处理交换相邻字节的一般情况,而不是一些固定数量的字节跨度> 【参考方案2】:

至少在 Visual C++ 中,你可以使用 _byteswap_ulong() 和朋友:http://msdn.microsoft.com/en-us/library/a3140177.aspx

这些函数被 VC++ 编译器视为内在函数,生成的代码会在可用时利用硬件支持。使用 VC++ 10.0 SP1,我看到以下为 x86 生成的代码:

return _byteswap_ulong(val);

mov     eax, DWORD PTR _val$[esp-4]
bswap   eax
ret     0

return _byteswap_ushort(val);

mov     ax, WORD PTR _val$[esp-4]
mov     ch, al
mov     cl, ah
mov     ax, cx
ret     0

【讨论】:

【参考方案3】:

谁说它做了太多的计算?

out = changeEndianness16(in);

gcc 4.6.0

movzwl  -4(%rsp), %edx
movl    %edx, %eax
movsbl  %dh, %ecx
movb    %cl, %al
movb    %dl, %ah
movw    %ax, -2(%rsp)

clang++ 2.9

movw    -2(%rsp), %ax
rolw    $8, %ax
movw    %ax, -4(%rsp)

Intel C/C++ 11.1

movzwl    4(%rsp), %ecx
rolw      $8, %cx
xorl      %eax, %eax
movw      %cx, 6(%rsp)

您的编译器会产生什么?

【讨论】:

尚未检查汇编代码...办公室现在没有工具... :( 请注意:rolw 指令比单个简单指令预期的要慢。 lists.gnu.org/archive/html/qemu-devel/2010-04/msg01234.html @Ben Voigt 很有可能,我主要是回应“多次计算”假设,并邀请在讨论微优化之前查看实际的编译器输出。顺便说一句,很好的答案。【参考方案4】:

16位版本交换功能我使用了如下代码:

_int16 changeEndianness16(__int16 val)

    return ((val & 0x00ff) << 8) | ((val & 0xff00) >> 8);
    

使用 g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 上面的代码在使用 g++ -O3 -S -fomit-frame-pointer test.cpp 编译时会产生以下(非内联)汇编代码:

movzwl  4(%esp), %eax
rolw    $8, %ax
ret

下面的代码是等价的,但是g++没有那么擅长优化。

__int16 changeEndianness16_2(__int16 val)

    return ((val & 0xff) << 8) | (val >> 8);

编译它会得到更多的 asm 代码:​​

movzwl  4(%esp), %edx
movl    %edx, %eax
sarl    $8, %eax
sall    $8, %edx
orl     %edx, %eax
ret

【讨论】:

您不会得到相同的代码,因为它实际上并不等效。第二个签名扩展会给出错误的结果(第一个版本也不正确,它是否有效取决于平台,特别是sizeof(int))。 @BenVoigt - 正如您评论的那样,为什么此答案中的第一个版本的代码不起作用? @goldenmean:在sizeof (int) == sizeof(__int16) 所在的系统上说val == 0x8000。现在第一项是 0,但 val &amp; 0xff000x8000(val &amp; 0xff00) &gt;&gt; 80xff80。现在0xff80 不是0x8000 的字节交换版本。

以上是关于改变字节顺序的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

字节顺序标记搞砸了 Java 中的文件读取

如何在 Swift 中使用 htonl 设置整数字节顺序?

对于字节顺序——大端与小端的理解

大端字节顺序和小端字节顺序有啥区别

大端模式与小端模式网络字节顺序与主机字节顺序

ASM字节码操作 Label 介绍 顺序选择和循环