【问题标题】:128-bit shifts using assembly language?使用汇编语言进行 128 位移位?
【发布时间】:2011-10-24 01:10:11
【问题描述】:

在现代 Intel CPU(核心 i7、沙桥)上进行 128 位移位的最有效方法是什么。

类似的代码在我最内层的循环中:

u128 a[N];
void xor() {
  for (int i = 0; i < N; ++i) {
    a[i] = a[i] ^ (a[i] >> 1) ^ (a[i] >> 2);
  }
}

a[N] 中的数据几乎是随机的。

【问题讨论】:

  • 您可以先开启最大优化并查看编译器生成的内容。
  • 你能告诉我们u128的定义吗?我可能可以使用 SSE 提供有效的解决方案。
  • Shift 内部函数在此处列出:msdn.microsoft.com/en-us/library/edy397f8.aspx
  • @Hans:他们都不会移动 128 位整数
  • 通常“移位”(如问题中的 c 运算符“>>”)表示移位,这就是所要求的。此外,英特尔的字节移位仅限于一个常数。如果您想按可变数量进行字节移位,则最好避免使用此指令,并执行未对齐的存储/加载,因为 i7 和更新版本的未对齐惩罚可以忽略不计。

标签: performance assembly x86 128-bit


【解决方案1】:

使用指令Shift Double

所以SHLDSHRD 指令,因为SSE ​​不是为此目的而设计的。 有一个经典的方法,这里有 32 位和 64 位 CPU 模式下 128 位左移 16 位的测试用例。

通过这种方式,您可以对最多 32/64 位执行无限大小的移位。 Yoo 可以移动立即数位或 cl 寄存器中的数字。第一条指令操作符也可以寻址内存中的变量。

32位x86 CPU模式下128位左移16位:

    mov     eax, $04030201;
    mov     ebx, $08070605;
    mov     ecx, $0C0B0A09;
    mov     edx, $100F0E0D;

    shld    edx, ecx, 16
    shld    ecx, ebx, 16
    shld    ebx, eax, 16
    shl     eax, 16

在64位x86 CPU模式下128位左移16位:

    mov    rax, $0807060504030201;
    mov    rdx, $100F0D0E0B0C0A09;

    shld   rdx, rax, 16
    shl    rax, 16

【讨论】:

  • 我用过这个。它可以工作并且速度相当快,但您应该提到 32 位代码允许最多移位 31,而 64 位代码最多允许移位 63。如果您想移位可变数量,则不能保证小于64,这个不能用。
  • @drhirsch:我提到了最多 32/64 位,当然如果您想要移动 32/64 位以上的字,它应该最多 31/63 位。
【解决方案2】:

在这种特殊情况下,您可以结合使用 x86 SHR 和 RCR 指令:

; a0 - bits 0-31 of a[i]
; a1 - bits 32-63 of a[i]
; a2 - bits 64-95 of a[i]
; a3 - bits 96-127 of a[i]
mov eax, a0
mov ebx, a1
mov ecx, a2
mov ecx, a3

shr eax, 1
rcr ebx, 1
rcr ecx, 1
rcr edx, 1

; b0 - bits 0-31 of b[i] := a[i] >> 1
; b1 - bits 32-63 of b[i] := a[i] >> 1
; b2 - bits 64-95 of b[i] := a[i] >> 1
; b3 - bits 96-127 of b[i] := a[i] >> 1
mov b0, eax
mov b1, ebx
mov b2, ecx
mov b3, edx

shr eax, 1
rcr ebx, 1
rcr ecx, 1
rcr edx, 1

; c0 - bits 0-31 of c[i] := a[i] >> 2 = b[i] >> 1
; c1 - bits 32-63 of c[i] := a[i] >> 2 = b[i] >> 1
; c2 - bits 64-95 of c[i] := a[i] >> 2 = b[i] >> 1
; c3 - bits 96-127 of c[i] := a[i] >> 2 = b[i] >> 1
mov c0, eax
mov c1, ebx
mov c2, ecx
mov c3, edx

如果您的目标是 x86-64,这将简化为:

; a0 - bits 0-63 of a[i]
; a1 - bits 64-127 of a[i]
mov rax, a0
mov rbx, a1

shr rax, 1
rcr rbx, 1

; b0 - bits 0-63 of b[i] := a[i] >> 1
; b1 - bits 64-127 of b[i] := a[i] >> 1
mov b0, rax
mov b1, rbx

shr rax, 1
rcr rbx, 1

; c0 - bits 0-63 of c[i] := a[i] >> 2 = b[i] >> 1
; c1 - bits 64-127 of c[i] := a[i] >> 2 = b[i] >> 1
mov c0, rax
mov c1, rbx

更新:更正了 64 位版本中的拼写错误

【讨论】:

  • 不幸的是,RCR/RCL 指令在几乎所有现代处理器上都异常缓慢。 SHLD/SHRD 是更好的选择
  • 而在第二种情况下 shr eax, 1; rcr ebx, 1 必须是 shr rax, 1; rcr rbx, 1
  • 当第二个参数为 1 时,RCR/RCL 很快。这正是这个问题的情况。当第二个参数为 1 时,RCR/RCL 在所有现代 CPU 上都比 SHLD/SHRD 快:
  • 当第二个参数为 1 时,在除 Sandy Bridge 和 Atom 之外的所有现代 CPU 上,RCR/RCL 比 SHLD/SHRD 快。
猜你喜欢
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-09
  • 1970-01-01
  • 1970-01-01
  • 2014-01-22
相关资源
最近更新 更多