[英]Convert _mm_clmulepi64_si128 to vmull_{high}_p64
我具有以下英特爾PCLMULQDQ內部函數 (無進位乘法):
__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(向量)的文檔不太清楚,我不確定結果會是什么,因為我不了解2的排列說明符。 ARM ACLE 2.0也不太有用:
poly128_t vmull_p64 (poly64_t, poly64_t);
對雙字低位執行加寬多項式乘法。 在ARMv8 AArch32和AArch64上可用。
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})。
由於您通過評論澄清了混淆的來源:
完全乘法產生的結果是輸入寬度的兩倍。 一個加號最多可以產生一個進位位,但是一個mul可以產生整個上半位。
乘法完全等於移位+加法,並且這些移位使位從一個操作數到最高2N-1(當輸入為N位寬時)。 請參閱Wikipedia的示例 。
在x86的mul
指令之類的常規整數乘法(帶有加法步驟)中,部分和的進位可以設置高位,因此結果恰好是寬度的兩倍。
XOR是不帶進位的加法運算,因此,無位乘法是相同的移位加法算法,但使用XOR而不是帶進位加法。 在無進位乘法中,沒有進位,因此全角結果的最高位始終為零。 英特爾甚至在pclmuludq
的x86 insn參考手冊的“操作”部分中明確指出了這pclmuludq
: DEST[127] ← 0;
。 該部分精確記錄了產生結果的所有移位和異或。
PMULL[2]
文檔對我來說似乎很清楚。 目的地必須是.8H
向量(表示八個16位(半字)元素)。 PMULL
的源必須是.8B
向量(8個1字節元素),而PMULL2
的源必須是.16B
(16個1字節元素,其中僅使用每個源的高8位)。
如果這是ARM32 NEON,其中每個16B向量寄存器的上半部分是一個奇數的較窄寄存器,則PMULL2
對任何事情都沒有用。
但是,沒有“操作”部分來確切描述哪些位與其他位相乘。 幸運的是, 鏈接在注釋中的論文很好地總結了 ARMv7,ARMv8 32和64位的可用指令 。 .8B / .8H組織說明符似乎是偽造的,因為PMULL
確實像SSE的pclmul指令一樣執行單個64x64-> 128無載mul。 ARMv7 VMULL.P8
NEON insn確實打包了8x8-> 16,但是很清楚PMULL
(和ARMv8 AArch32 VMULL.P8
)是不同的。
太糟糕了,ARM文檔沒有透露任何信息。 似乎非常缺乏,尤其是。 誤導.8B
矢量組織的東西。 該論文顯示了一個使用預期的.1q
和.1d
(和.2d
)組織的示例,因此匯編器可能並不在乎您認為數據意味着什么,只要它的大小合適即可。
要進行高與低乘法運算,您需要對其中之一進行移位。
例如, 如果您需要所有四個組合 (a0 * b0,a1 * b0,a0 * b1,a1 * b1),就像您要構建一個128x128-> 128乘以64x64-> 128乘以(使用唐津),您可以這樣做:
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的可變長度機器編碼那樣僅僅增加額外的立即數,所以這可能不是一個選擇。
同一件事的另一個版本,帶有真正的隨機播放指令,之后是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
如何將_mm_clmulepi64_si128轉換為vmull_ {high} _p64?
這是下面的示例程序的結果。 轉換為:
_mm_clmulepi64_si128(a, b, 0x00)
→ vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x01)
→ vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 0))
_mm_clmulepi64_si128(a, b, 0x10)
→ vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 1))
_mm_clmulepi64_si128(a, b, 0x11)
→ vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 1))
對於情況(4) _mm_clmulepi64_si128(a, b, 0x11)
,以下內容也成立:
_mm_clmulepi64_si128(a, b, 0x11)
→ vmull_high_p64((poly64x2_t)a, (poly64x2_t)b)
我猜想如果不小心,情況(1)到(4)可能會溢出到內存中,因為vgetq_lane_u64
返回標量或非向量類型。 我還猜想情況(5)傾向於保留在Q寄存器中,因為它是向量類型。
x86_64和_mm_clmulepi64_si128 :
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
ARM64和vmull_p64 :
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x606060606060606, r[1]: 0x606060606060606
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xa0a0a0a0a0a0a0a, r[1]: 0xa0a0a0a0a0a0a0a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0xc0c0c0c0c0c0c0c, r[1]: 0xc0c0c0c0c0c0c0c
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222222222222, a[1]: 0x4444444444444444
b[0]: 0x3333333333333333, b[1]: 0x5555555555555555
r[0]: 0x1414141414141414, r[1]: 0x1414141414141414
示例程序mul-sse-neon.cc :
#define IS_ARM (__arm__ || __arm32__ || __aarch32__ || __arm64__ || __aarch64__)
#define IS_X86 (__i386__ || __i586__ || __i686__ || __amd64__ || __x86_64__)
#if (IS_ARM)
# include <arm_neon.h>
# if defined(__ARM_ACLE) || defined(__GNUC__)
# include <arm_acle.h>
# endif
#endif
#if (IS_X86)
# include <emmintrin.h>
# if defined(__GNUC__)
# include <x86intrin.h>
# endif
#endif
#if (IS_ARM)
typedef uint64x2_t word128;
#elif (IS_X86)
typedef __m128i word128;
#else
# error "Need a word128"
#endif
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
void print_val(const word128* value, const char* label);
/* gcc -DNDEBUG -g3 -O0 -march=native mul-sse-neon.cc -o mul-sse-neon.exe */
/* gcc -DNDEBUG -g3 -O0 -march=armv8-a+crc+crypto mul-sse-neon.cc -o mul-sse-neon.exe */
int main(int argc, char* argv[])
{
#if (IS_ARM)
printf("IS_ARM: true\n");
#elif (IS_X86)
printf("IS_X86: true\n");
#endif
word128 a,b, r;
a[0] = 0x2222222222222222, a[1] = 0x4444444444444444;
b[0] = 0x3333333333333333, b[1] = 0x5555555555555555;
#if (IS_ARM)
printf("****************************************\n");
printf("vmull_p64(a, b, 0x00)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x01)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x10)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,0));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("vmull_p64(a, b, 0x11)\n");
r = (uint64x2_t)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b,1));
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#elif (IS_X86)
printf("****************************************\n");
printf("clmulepi64(a, b, 0x00)\n");
r = _mm_clmulepi64_si128(a, b, 0x00);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x01)\n");
r = _mm_clmulepi64_si128(a, b, 0x01);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x10)\n");
r = _mm_clmulepi64_si128(a, b, 0x10);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
printf("****************************************\n");
printf("clmulepi64(a, b, 0x11)\n");
r = _mm_clmulepi64_si128(a, b, 0x11);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
#endif
return 0;
}
static const word128 s_v = {0,0};
static const char s_l[] = "";
void print_val(const word128* value, const char* label)
{
const word128* v = (value ? value : &s_v);
const char* l = (label ? label : s_l);
#if (IS_ARM)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#elif (IS_X86)
printf("%s[0]: 0x%" PRIx64 ", %s[1]: 0x%" PRIx64 "\n", l, (*v)[0], l, (*v)[1]);
#endif
}
vmull_high_p64
的代碼如下。 它總是產生相同的結果,因為它總是采用相同的高詞:
printf("****************************************\n");
printf("vmull_p64(a, b)\n");
r = (uint64x2_t)vmull_high_p64((poly64x2_t)a, (poly64x2_t)b);
print_val(&a, "a"); print_val(&b, "b"); print_val(&r, "r");
為了完整起見,請將數據切換到:
word128 a,b, r;
a[0] = 0x2222222233333333, a[1] = 0x4444444455555555;
b[0] = 0x6666666677777777, b[1] = 0x8888888899999999;
產生以下結果:
$ ./mul-sse-neon.exe
IS_X86: true
****************************************
clmulepi64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
clmulepi64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
clmulepi64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
clmulepi64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c
和:
$ ./mul-sse-neon.exe
IS_ARM: true
****************************************
vmull_p64(a, b, 0x00)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0xd0d0d0d09090909, r[1]: 0xc0c0c0c08080808
****************************************
vmull_p64(a, b, 0x01)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x111111111b1b1b1b, r[1]: 0x101010101a1a1a1a
****************************************
vmull_p64(a, b, 0x10)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x191919191b1b1b1b, r[1]: 0x181818181a1a1a1a
****************************************
vmull_p64(a, b, 0x11)
a[0]: 0x2222222233333333, a[1]: 0x4444444455555555
b[0]: 0x6666666677777777, b[1]: 0x8888888899999999
r[0]: 0x212121212d2d2d2d, r[1]: 0x202020202c2c2c2c
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.