两个 16 位整数向量与 C++ 中的 AVX2 的内积

Posted

技术标签:

【中文标题】两个 16 位整数向量与 C++ 中的 AVX2 的内积【英文标题】:Inner product of two 16bit integer vectors with AVX2 in C++ 【发布时间】:2020-05-27 10:53:31 【问题描述】:

我正在寻找最有效的方法来将两个对齐的 int16_t 数组相乘,其长度可以用 AVX2 除以 16。

在乘以向量x 后,我从_mm256_extracti128_si256_mm256_castsi256_si128 开始得到x 的低和高部分,然后用_mm_add_epi16 相加。

我复制了结果寄存器并将_mm_move_epi64 应用到原始寄存器并再次添加_mm_add_epi16。现在,我想我有:-, -, -, -, x15+x7+x11+x3, x14+x6+x10+x2, x13+x5+x9+x1, x12+x4+x8+x0 在 128 位寄存器内。但是现在我卡住了,不知道如何有效地总结剩余的四个条目以及如何提取 16 位结果。

【问题讨论】:

_mm_move_epi64 不是水平总和的有用部分。此外,您没有描述如何从 16 位 int 元素中获得 _mm_add_ps 的浮点数。 _mm_add_epi16 用于水平总和的一步但_ps 用于另一步几乎没有意义。但无论如何,使用pmaddwd 进行整数乘法步骤以水平累加到 32 位元素中,将 hsum 简化为 32 位元素并提供更多范围而不会超限。无论如何,如果溢出不是问题,您想在垂直 pmaddwd / paddd 循环之后进行 hsum。 Fastest way to do horizontal SSE vector sum (or other reduction) 谢谢彼得,我知道这个帖子,但我在那里找不到我的特定数据类型的解决方案(如果答案已经存在,那么我不明白)。跨度> 是的,彼得,我犯了一些复制和粘贴错误,所以我没有对浮动进行任何转换。全部在 epi16/integer 中。 好的,如果这样是安全的(不会溢出),那么最好在循环中最初与 _mm256_madd_epi16_mm_add_epi32 相乘,这样你就可以在外面使用 32 位 hsum .无论如何,实现 16 位 hsum 的最佳方法是使用 _mm_madd_epi16 作为第一步(如 hsum 链接中所述),但实际上使用它的全部功能作为您的乘法器而不是使用虚拟的 1 乘法器会更好。跨度> 【参考方案1】:

按照谷歌的 cmets 和小时数,我的工作解决方案:

// AVX multiply
hash = 1;
start1 = std::chrono::high_resolution_clock::now();
for(int i=0; i<2000000; i++) 
    ZTYPE*  xv = al_entr1.c.data();
    ZTYPE*  yv = al_entr2.c.data();

    __m256i tres = _mm256_setzero_si256();
    for(int ii=0; ii < MAX_SIEVING_DIM; ii = ii+16/*8*/)
    
        // editor's note: alignment required.  Use loadu for unaligned
        __m256i  xr = _mm256_load_si256((__m256i*)(xv+ii));
        __m256i  yr = _mm256_load_si256((__m256i*)(yv+ii));
        const __m256i tmp = _mm256_madd_epi16 (xr, yr);
        tres =  _mm256_add_epi32(tmp, tres);
    

    // Reduction
    const __m128i x128 = _mm_add_epi32  ( _mm256_extracti128_si256(tres, 1), _mm256_castsi256_si128(tres));
    const __m128i x128_up = _mm_shuffle_epi32(x128, 78);
    const __m128i x64  = _mm_add_epi32  (x128, x128_up);
    const __m128i _x32 =  _mm_hadd_epi32(x64, x64);

    const int res = _mm_extract_epi32(_x32, 0);
    hash |= res;


finish1 = std::chrono::high_resolution_clock::now();
elapsed1 = finish1 - start1;
std::cout << "AVX multiply: " <<elapsed1.count() << " sec. (" << hash << ")" << std::endl;

这至少是迄今为止最快的解决方案:

std::inner_product: 0.819781 秒。 (-14335) std::inner_product(对齐):0.964058 秒。 (-14335) 天真的乘法:0.588623 秒。 (-14335) 展开乘法:0.505639 秒。 (-14335) AVX 乘法:0.0488352 秒。 (-14335)

【讨论】:

以上是关于两个 16 位整数向量与 C++ 中的 AVX2 的内积的主要内容,如果未能解决你的问题,请参考以下文章

使用 AVX2 对 2 个短整型向量进行向量加法

如何在两个 AVX2 向量之间交换 128 位部分

用 SSE 在 C++ 中将两个 32 位整数向量相乘的最快方法

AVX2 64位无符号整数比较

有没有办法用 AVX2 编写 _mm256_shldi_epi8(a,b,1) ? (向量之间每 8 位元素移位一位)

如何在 AVX 中使用融合乘法和加法来处理 16 位压缩整数