jww*_*jww 4 c intrinsics arm64
我有以下Intel PCLMULQDQ 内在函数(无进位乘法):
__m128i a, b; // Set to some value
__m128i r = _mm_clmulepi64_si128(a, b, 0x10);
Run Code Online (Sandbox Code Playgroud)
该0x10告诉我乘的是:
r = a[63:0] * b[127:64]
Run Code Online (Sandbox Code Playgroud)
我需要将其转换为 NEON(或更准确地说,使用 Crypto 扩展名):
poly64_t a, b; // Set to some value
poly16x8_t = vmull_p64(...) or vmull_high_p64(...);
Run Code Online (Sandbox Code Playgroud)
我认为vmull_p64适用于低 64 位,而适用vmull_high_p64于高 64 位。我想我需要将值 128 位值之一移动到 mim _mm_clmulepi64_si128(a, b, 0x10)。PMULL、PMULL2(向量)的文档不太清楚,我不确定结果会是什么,因为我不了解 2 的排列说明符。在ARM ACLE 2.0是不是太有用之一:
Run Code Online (Sandbox Code Playgroud)poly128_t vmull_p64 (poly64_t, poly64_t);对双字低部分执行加宽多项式乘法。在 ARMv8 AArch32 和 AArch64 上可用。
Run Code Online (Sandbox Code Playgroud)poly128_t vmull_high_p64 (poly64x2_t, poly64x2_t);对双字高部分执行加宽多项式乘法。在 ARMv8 AArch32 和 AArch64 上可用。
我如何转换_mm_clmulepi64_si128为vmull_{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 位宽时)。请参阅维基百科的示例。
在像x86 的mul指令这样的普通整数乘法(在加法步骤中有进位)中,部分和的进位可以设置高位,因此结果正好是两倍宽。
XOR 是没有进位的加法,因此无进位乘法是相同的移位加法算法,但使用 XOR 而不是加进位。在无进位乘法中,没有进位,因此全角结果的最高位始终为零。英特尔甚至使这个明确的在x86的insn裁判手册的操作部分pclmuludq: DEST[127] ? 0;。该部分精确地记录了产生结果的所有移位和异或。
这些PMULL[2]文档对我来说似乎很清楚。目标必须是一个.8H向量(这意味着八个 16 位(半字)元素)。的来源PMULL必须是.8B向量(8 个一字节元素),而来源PMULL2必须是.16B(16 个一字节元素,其中仅使用每个来源的前 8 个元素)。
如果这是 ARM32 NEON,其中每个 16B 向量寄存器的上半部分是奇数编号的窄寄存器,则PMULL2对任何事情都没有用。
但是,没有“操作”部分来准确描述哪些位与哪些其他位相乘。幸运的是,评论中链接的论文很好地总结了ARMv7 和 ARMv8 32 位和 64 位的可用指令。.8B / .8H 组织说明符似乎是假的,因为PMULL确实像 SSE 的pclmul指令那样执行单个 64x64 -> 128 无进位 mul 。ARMv7 VMULL.P8NEON insn 确实做了一个打包的 8x8->16,但清楚地表明PMULL(和 ARMv8 AArch32 VMULL.P8)是不同的。
ARM 文档没有说任何这些太糟糕了;它似乎非常缺乏,尤其是。重新误导.8B向量组织的东西。那篇论文展示了一个使用预期.1q和.1d(和.2d)组织的例子,所以也许汇编器并不关心你认为你的数据意味着什么,只要它的大小合适。
要进行高低相乘,您需要移动其中一个。
例如,如果您需要所有四种组合(a0*b0、a1*b0、a0*b1、a1*b1),就像构建 128x128 -> 64x64 中的 128 个乘法 -> 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
Run Code Online (Sandbox Code Playgroud)
因此,看起来 ARM 的设计选择包括下下和上上,但不包括交叉乘法指令(或像 x86 那样的选择器常量)并不会导致效率低下。而且由于 ARM 指令不能像 x86 的可变长度机器编码那样添加额外的立即数,所以这可能不是一个选项。
同样的事情的另一个版本,有一个真正的 shuffle 指令和 Karatsuba 之后(从在 ARMv8 上实现 GCM逐字复制)。但仍然是编造的寄存器名称。该论文沿途重用了相同的临时寄存器,但我已经按照我为 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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
735 次 |
| 最近记录: |