【问题标题】:Why the ror op in this inline assembly not working properly?为什么这个内联汇编中的 ror op 不能正常工作?
【发布时间】:2011-06-08 21:51:21
【问题描述】:

我编写了一个程序来处理一些以大端格式写入磁盘的数据,因此该程序需要交换字节才能执行其他操作。在分析代码后,我发现我的字节交换函数占用了 30% 的执行时间。所以我想自己,我怎样才能加快速度呢? 所以我决定写一个小块内联汇编。

我会替换这个:

void swapTwoByte(char* array, int numChunks)
{
    for(int i= (2*numChunks-1); i>=0; i-=2)
    {
        char temp=array[i];
        array[i]=array[i-1];
        array[i-1]=temp;
    }
}

用这个:

void swapTwoByte(int16* array, int numChunks)
{
    for(int i= (numChunks-1); i>=0; --i)
    {
        asm("movw %1, %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "rorw %%ax;"
            "movw %%ax, %0;"
            : "=r" ( array[i] )
            : "r" (array[i])
            :"%ax"
        );
    }
}

这是预期的工作,但这是很多旋转操作。

所以这是我的问题: 根据this source rorw 可以采用两个操作数,在气体语法中,源操作数应该是要旋转的位数,但是每次我尝试用类似的东西替换 8 个旋转权限的列表时

".set rotate, 0x0008"
"rorw rotate, %%ax"

我收到一个汇编程序错误说明:

"Error: number of operands mismatch for `ror'"

这是为什么?我错过了什么?

【问题讨论】:

  • 您是否尝试过更简单的优化,例如通过数组迭代 forwards 以进行字节交换?
  • 你可能想试试 bswap 顺便说一句..
  • 据我了解,向前迭代可能会更慢。通过在零处停止,我使前瞻逻辑更容易工作。至少我被告知是这样的。
  • 你在做什么处理?整个事情真的慢到足以证明认真的优化工作是合理的吗?在我看来,这已经足够快了,你真正被告知的是“嘿,你实际上并没有做那么多处理”......
  • 处理数百个 GRETINA 输出的数据。代码需要 3 个小时才能执行,所以我正在寻找任何可以让它更快的方法。

标签: c++ assembly inline-assembly


【解决方案1】:

首先,使用

#include <arpa/inet.h>
little_endian = ntohs(big_endian);

这将在您使用的任何系统上编译成最佳代码,如果您碰巧将代码移植到大端平台,它甚至可以工作。

但是,这不会解决您的性能问题,因为我认为您错误地识别了问题。 Nemo 微优化的第一条规则:“数学快,内存慢”。

遍历一大块内存并交换其字节对缓存非常不友好。一个字节交换是一个周期;除非命中缓存,否则内存读取或写入需要数百个周期。

所以在你使用它们之前不要交换字节。我个人最喜欢的方法是:

class be_uint16_t {
public:
        be_uint16_t() : be_val_(0) {
        }
        be_uint16_t(const uint16_t &val) : be_val_(htons(val)) {
        }
        operator uint16_t() const {
                return ntohs(be_val_);
        }
private:
        uint16_t be_val_;
} __attribute__((packed));

这定义了一个代表内存中大端序数的两字节类。它根据需要隐式转换为 uint16_t 和从 uint16_t 转换。因此,将您的内存指针转换为be_uint16 *,然后像访问数组一样访问它;忘记字节交换,因为课程会为你做:

const be_uint16_t *p = (be_uint16 *)my_block;
unsigned val = p[37];  // or whatever

请注意,您甚至可以执行以下操作:

be_uint16_t x = 12;
x = x + 1;
write(fd, &x, sizeof(x)); // writes 13 to file in big-endian form

根据我的经验,在使用前立即交换值的开销是无法察觉的。地方性是游戏的名称...

【讨论】:

  • @DeadMG- 谢谢。我怀疑迷失在噪音中,但我一直在寻找机会分享我的“be_uint16_t”小工具:-)
  • 我不知道这个小工具,但“在需要它们之前不要交换字节是有道理的”。为此,当我将数据实际解释为类时,我正在重写一些东西以添加交换
【解决方案2】:

考虑稍微重新组织 C++ 代码。如所写,g++ 4.5.2 将它编译为一个无聊的紧循环,带有四个 8 位 movs 和两个指针减量。

.L3:
    movzbl  (%rdi), %eax
    movzbl  -1(%rdi), %edx
    movb    %al, -1(%rdi)
    movb    %dl, (%rdi)
    subq    $2, %rdi
    subl    $2, %esi
    jns .L3

改写成

void swapTwoByte(char* array, int numChunks)
{
    for(int i = 0; i<numChunks*2; i+=2)
        std::swap(array[i], array[i+1]);
}

让编译器意识到您在做什么并开启完整的 SIMD 功能,核心循环现在一次处理 32 个字节:

.L4:
    movdqu  (%rdx), %xmm1
    movdqu  (%rax), %xmm2
    movdqa  %xmm1, %xmm0
    movdqa  %xmm2, %xmm3
    pshufb  %xmm7, %xmm0
    pshufb  %xmm4, %xmm2
    pshufb  %xmm6, %xmm3
    pshufb  %xmm5, %xmm1
    por %xmm3, %xmm0
    por %xmm2, %xmm1
    incl    %ecx
    movdqa  %xmm1, %xmm2
    punpckhbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqu  %xmm2, (%rdx)
    movdqu  %xmm1, (%rax)
    addq    $32, %rdx
    addq    $32, %rax
    cmpl    %ecx, %r8d
    ja  .L4

rorw 不会打败它。

【讨论】:

  • 试过了,使用swap我只得到了一点性能上的提升,从swap代码占用28.32%的时间到26.11%
【解决方案3】:

交换指令适用于 32 位值。要交换 word 中的两个字节,请使用 xchg al,ah 指令。

【讨论】:

  • 当你可以使用 bswap 时为什么要这样做
  • @Mike: 同意 bswap 应该在 x86 下编译为 "xchg al,ah" 指令。
猜你喜欢
  • 2011-10-28
  • 2021-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-30
  • 2023-02-20
  • 1970-01-01
  • 2022-10-23
相关资源
最近更新 更多