簡體   English   中英

將_mm_clmulepi64_si128轉換為vmull_ {high} _p64

[英]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參考手冊的“操作”部分中明確指出了這pclmuludqDEST[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?

這是下面的示例程序的結果。 轉換為:

  1. _mm_clmulepi64_si128(a, b, 0x00)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 0))

  2. _mm_clmulepi64_si128(a, b, 0x01)vmull_p64(vgetq_lane_u64(a, 1), vgetq_lane_u64(b, 0))

  3. _mm_clmulepi64_si128(a, b, 0x10)vmull_p64(vgetq_lane_u64(a, 0), vgetq_lane_u64(b, 1))

  4. _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) ,以下內容也成立:

  1. _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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM