gpt4 book ai didi

将 _mm_clmulepi64_si128 转换为 vmull_{high}_p64

转载 作者:行者123 更新时间:2023-12-04 09:32:50 24 4
gpt4 key购买 nike

我有以下 Intel PCLMULQDQ intrinsic (无携带乘法):

__m128i a, b;   // Set to some value
__m128i r = _mm_clmulepi64_si128(a, b, 0x10);
0x10告诉我乘法是:
r = a[63:0] * b[127:64]

我需要将其转换为 NEON(或更准确地说,使用 Crypto 扩展名):
poly64_t a, b;   // Set to some value
poly16x8_t = vmull_p64(...) or vmull_high_p64(...);

我想 vmull_p64适用于低 64 位,而 vmull_high_p64对高 64 位进行操作。我想我需要将值之一移动 128 位值来模仿 _mm_clmulepi64_si128(a, b, 0x10) . PMULL, PMULL2 (vector) 的文档不太清楚,我不确定结果会是什么,因为我不明白 2 的排列说明符。 ARM ACLE 2.0也不是很有帮助:

poly128_t vmull_p64 (poly64_t, poly64_t);

Performs widening polynomial multiplication on double-words low part. Available on ARMv8 AArch32 and AArch64.

poly128_t vmull_high_p64 (poly64x2_t, poly64x2_t);

Performs widening polynomial multiplication on double-words high part. Available on ARMv8 AArch32 and AArch64.



我如何转换 _mm_clmulepi64_si128vmull_{high}_p64 ?

对于任何考虑投资 NEON、PMULL 和 PMULL2 的人... 64 位乘法器和多项式支持是值得的。基准测试显示 GMAC 的 GCC 代码从 12.7 cpb 和 90 MB/s (C/C++) 下降到 1.6 cpb 和 670 MB/s(NEON 和 PMULL{2})。

最佳答案

由于您通过评论澄清了混淆的来源:

一个完整的乘法产生的结果是输入宽度的两倍。 add 最多可以产生一个进位位,但 mul 产生整个上半部分。

乘法完全等同于移位 + 加法,这些移位使一个操作数中的位高达 2N - 1(当输入为 N 位宽时)。见 Wikipedia's example .

在正常的整数乘法中(在加法步骤中带有进位),如 x86's mul instruction , 部分和的进位可以设置高位,因此结果正好是两倍宽。

XOR 是没有进位的加法,因此无进位乘法是相同的移位加法算法,但使用 XOR 而不是加进位。在无进位乘法中,没有进位,因此全角结果的最高位始终为零。英特尔甚至在 pclmuludq 的 x86 insn 引用手册的操作部分明确说明了这一点。 :DEST[127] ← 0; .该部分精确地记录了产生结果的所有移位和异或。
PMULL[2]文档对我来说似乎很清楚。目的地必须是 .8H vector (这意味着八个 16 位(半字)元素)。 PMULL 的来源必须是.8B vector (8 个一字节元素),而 PMULL2 的来源必须是.16B (16 个单字节元素,其中仅使用每个源的高 8 个)。

如果这是 ARM32 NEON,其中每个 16B vector 寄存器的上半部分是一个奇数的窄寄存器,PMULL2对任何事情都没有用。

但是,没有“操作”部分来准确描述哪些位与哪些其他位相乘。幸运的是, paper linked in comments很好地总结了可用的说明 适用于 ARMv7 和 ARMv8 32 位和 64 位。 .8B/.8H 组织说明符似乎是假的,因为 PMULL确实像 SSE 一样执行单个 64x64 -> 128 无携带 mul pclmul操作说明。 ARMv7 VMULL.P8 NEON insn 确实做了一个打包的 8x8->16,但明确表示 PMULL (和 ARMv8 AArch32 VMULL.P8 )是不同的。

ARM 文档没有说任何这些太糟糕了;它似乎非常缺乏,尤其是。重新误导 .8B vector 组织的东西。那篇论文展示了一个使用预期 .1q 的例子。和 .1d (和 .2d )组织,所以也许汇编器并不关心你认为你的数据意味着什么,只要它的大小合适。

要进行高低相乘,您需要移动其中一个。

例如,如果您需要所有四种组合 (a0*b0, a1*b0, a0*b1, a1*b1),就像你构建一个 128x128 -> 128 乘以 64x64 -> 128 乘法(使用 Karatsuba)一样,你可以这样做:

pmull   a0b0.8H, a.8B,  b.8B
pmull2 a1b1.8H, a.16B, b.16B
swap a's top and bottom half, which I assume can be done efficiently somehow
pmull a1b0.8H, swapped_a.8B, b.8B
pmull2 a0b1.8H, swapped_a.16B, b.16B

因此,看起来 ARM 的设计选择包括下下和上上,但不包括交叉乘法指令(或像 x86 那样的选择器常量)不会导致效率低下。而且由于 ARM 指令不能像 x86 的可变长度机器编码那样添加额外的立即数,所以这可能不是一个选项。

同样的事情的另一个版本,有一个真正的 shuffle 指令和 Karatsuba 之后(从 Implementing GCM on ARMv8 逐字复制)。但仍然是编造的寄存器名称。这篇论文在此过程中重复使用了相同的临时寄存器,但我已经按照我为 C 内在函数版本命名的方式命名了它们。这使得扩展精度乘法的操作非常清楚。编译器可以为我们重用死寄存器。
1:  pmull    a0b0.1q, a.1d, b.1d
2: pmull2 a1b1.1q, a.2d, b.2d
3: ext.16b swapped_b, b, b, #8
4: pmull a0b1.1q, a.1d, swapped_b.1d
5: pmull2 a1b0.1q, a.2d, swapped_b.2d
6: eor.16b xor_cross_muls, a0b1, a1b0
7: ext.16b cross_low, zero, xor_cross_muls, #8
8: eor.16b result_low, a0b0, cross_low
9: ext.16b cross_high, xor_cross_muls, zero, #8
10: eor.16b result_high, a1b1, cross_high

关于将 _mm_clmulepi64_si128 转换为 vmull_{high}_p64,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38553881/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com