【问题标题】:How to use assembly POPCNT instruction when running on a 32 bits Ubuntu在 32 位 Ubuntu 上运行时如何使用汇编 POPCNT 指令
【发布时间】:2023-03-20 02:32:01
【问题描述】:

对于一个特定项目,我坚持使用 gcc 和在 i7 Core 上运行的 32 位 12.04 LTS Ubuntu,最多支持 AVX SIMD 指令。

由于是 32 位操作系统,我显然不能使用在 256 位上运行的 AVX 指令。我可以使用 128 位访问 SSE4.2 指令,并且 POPCNT 可以在 16、32 和 64 位数据上运行,因此看起来很有希望。但我尝试了几种方法向 POPCNT 提供 64 位数据,但均未成功。 GCC 4.6.3 返回

  • “未知的寄存器名称” 用于 r8 到 r15,
  • rax-rdx 的“错误寄存器名称”
  • 当试图提供 mm 寄存器或为我的内联汇编函数提供一些 uint64 或 long long 时,这些 uint64 或 long long 会受到此类寄存器的影响

方式:

uint64 a, b;
__asm__ volatile (“POPCNT %1, %0;”
            :”=r”(b)
            :”r”(a)
            :
        )

gcc 告诉 “popcnt 的操作数类型不匹配”,

  • 写 POPCNTQ 会导致“popcnt 的指令后缀无效”

如果 POPCNT 支持 128 位 xmm 寄存器,那就太好了...

在汇编中对 64 位数据应用 POPCNT 的任何解决方法?

PS关于使用 shuffle 的 SSSE3 popcount 与 SSE4 POPCNT 性能比较的讨论在这里找到了结论http://danluu.com/assembly-intrinsics/ 并且仅仅是因为使用内在函数并不总是提供有效的汇编代码。使用内部函数快速优化 C/C++ 代码非常好,如果这足以满足需求,那很好。但是,与内在函数相比,我在汇编中使用 shuffle 获得了近 30% 的性能改进编码 popcount。

【问题讨论】:

  • 我可能弄错了,但是在 32 位模式下,RAX 和 R15 到底是什么意思?它们是 GP 64 位寄存器的名称,根据定义,您在 32 位模式下没有这些名称。宽寄存器是向量“XMM”寄存器。虽然"r"(a) 似乎是一个无辜的语法,但它确实要求a 适合GP 寄存器。
  • 请注意,您使用的智能引号是 C 和 C++ 中的无效字符,因此甚至无法编译

标签: gcc assembly optimization 32bit-64bit hammingweight


【解决方案1】:

popcnt 是一个整数指令。因此,在 32 位模式下,您不能将其与 64 位操作数一起使用。您需要计算两半的popcnt 并将它们加在一起。这就是我测试过的所有 clang 版本为内置函数所做的。但是,我无法获得任何 gcc 版本来使用 popcnt 指令。因此,虽然通常建议使用内置函数,但在这种情况下,内联 asm 可能会更好。

【讨论】:

  • 嗯,这很尴尬,我可以使用 SSEx 在 32 位操作系统上处理多达 128 位的打包数据,而组装 POPCNT 无法处理 64 位数据,即使打包:-s
  • @user3581220 为什么要在 32 位模式下处理 64 位值,而那里甚至没有 64 位寄存器?
  • 因为在 32 位操作系统上可以访问 128 位 xmm 寄存器(考虑到打包的值并不是什么大问题)。
  • @user3581220 Intel 决定让 XMM 寄存器在处理器的所有操作模式下都可以访问,但 AMD 决定只允许在 64 位长模式下访问整型寄存器的完整 64 位。
  • @user3581220 XMM 寄存器不是用于大的 128 位数字,而是用于同时存储多个值。它们有不同的用法,因为 XMM 不是通用寄存器。此外,使用 64 位寄存器需要 REX 前缀,它采用 inc 和 dec 的操作码。这就是为什么不能在 64 位模式下使用单字节 inc/dec 的原因。原因是 x86 中的前缀和操作码几乎没有代码点了
【解决方案2】:

32 位系统不支持 64 位 POPCOUNT,因为

REX 前缀仅在长模式下可用。 (不在 32 位操作系统中)

因此

写 POPCNTQ 会导致“popcnt 的指令后缀无效”。

请参见此处:http://www.felixcloutier.com/x86/POPCNT.html(以下引用)

Opcode          Instruction         Op/En   64-Bit Mode  Compat/Leg Mode    Description
F3 0F B8 /r     POPCNT r16, r/m16   RM      Valid        Valid           POPCNT on r/m16
F3 0F B8 /r     POPCNT r32, r/m32   RM      Valid        Valid           POPCNT on r/m32
F3 REX.W 0F B8 /r POPCNT r64,r/m64  RM      Valid        N.E.            POPCNT on r/m64

解决方法是将 64/128 位拆分为两个/四个 32 位指令:

; a=uint_64, 64 bit operand, little endian
popcount eax, dword ptr [a]
popcount edx, dword ptr [a+4]
add eax, edx
xor edx, edx      ; for first mov below
mov dword ptr [b], edx      ; not neccessary, only due to 64 target op (will there ever be 2^64 bits set???)
mov dword ptr [b+4], eax

编辑:MASM32 代码中(二进制)HammingDistance 的 64 位操作数大小版本:

Hamming_64 PROC word1:QWORD , word2: QWORD
  mov ecx, dword ptr [word1]
  mov edx, dword ptr [word1+4]
  xor ecx, dword ptr [word2]
  xor edx, dword ptr [word2+4]
  popcnt eax, ecx 
  popcnt ebx, edx
  add eax, ebx   ; returns distance in EAX
  ret
Hamming_64 ENDP

【讨论】:

  • 我之前在镜像站点上看到过这个文档,但我想确定没有人找到使用 64 位版本的方法。
  • 好吧,我当前的组装方法实际上是对两个 64 字节向量进行 XOR,然后处理汉明距离,所以我尝试以两种不同的方式将 shuffle 替换为 32 位 POPCNT:a) 回写在处理 POPCNT 之前,从 xmm 到对齐数组的 XOR 结果,b) 将 XOR 结果 xmm 寄存器移动 4 个字节,并在处理该寄存器上的 POPCNT 之前将其放置在 32 位 exx 寄存器中。安排代码中使用的寄存器以考虑指令延迟和吞吐量,我无法改进 shuffle 方法(实际上我损失了 5% 到 15%)。
  • 我仍然不确定您要实现什么或您要实现什么算法。无论如何,我在帖子中添加了一个 32 位版本,它在不使用 xmm 寄存器的情况下计算两个 64 位值(QWord)的汉明距离。非常简单,应该很快。
  • 我也试过了。使用 SSSE3 shuffle 与 popcount 的差异仅为 1-2%。
  • 但是这次我测试了在每个内核上运行 OpenMP 一个线程用于 32 位 XOR 和 POPCOUNT 使用 32 位 GP 寄存器,另一个线程用于 128 位 XOR 和 SSSE3 shuffle popcount 使用xmm 寄存器。一个核心处理 200.000 个向量在 for 循环中首先将线程拆分为 [0, 100.000[ 和 [100.000, 200.000[ ,然后作为 for(i=0; i
【解决方案3】:

我不知道是否有 32 位 popcnt 指令,但我敢打赌你不能在 32 位代码中使用 64 位 popcnt。尝试将 a 和 b 声明为 uint32_t。 BTW uint64_t 是标准C, uint64 不是。

【讨论】:

  • 是的,有。但与我当前使用 shuffle 的 SSSE3 汇编实现相比,我会错过很大一部分(全部?)改进。
  • 我对 uint64_t 的错误。写它只是为了展示我如何将参数传递给汇编函数寄存器。
【解决方案4】:

在使用汇编实现 32 位 POPCNT 之后,与 SSSE3 shuffle 汇编方法相比,看起来并没有真正的改进。 正如我所怀疑的,只有 64 位 POPCNT 版本几乎可以将速度提高一倍。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 2012-10-22
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 2019-08-06
    • 2016-06-13
    相关资源
    最近更新 更多