C 如何水平3乘3添加AVX2向量?

C 如何水平3乘3添加AVX2向量?,c,x86,simd,intrinsics,avx2,C,X86,Simd,Intrinsics,Avx2,我有一个包含16x16位元素的\uuuu m256i向量。我想在它上面应用一个三个相邻的水平加法。在标量模式下,我使用以下代码: unsigned short int temp[16]; __m256i sum_v;//has some values. 16 elements of 16-bit vector. | 0 | x15 | x14 | x13 | ... | x3 | x2 | x1 | _mm256_store_si256((__m256i *)&temp[0], su

我有一个包含16x16位元素的
\uuuu m256i
向量。我想在它上面应用一个三个相邻的水平加法。在标量模式下,我使用以下代码:

unsigned short int temp[16];
__m256i sum_v;//has some values. 16 elements of 16-bit vector.   | 0 | x15 | x14 | x13 | ... | x3 | x2 | x1 |
_mm256_store_si256((__m256i *)&temp[0], sum_v);
output1 = (temp[0] + temp[1] + temp[2]);
output2 = (temp[3] + temp[4] + temp[5]);
output3 = (temp[6] + temp[7] + temp[8]);
output4 = (temp[9] + temp[10] + temp[11]);
output5 = (temp[12] + temp[13] + temp[14]); 
// Dont want the 15th element
因为这部分放在我程序的瓶颈部分,所以我决定使用AVX2进行矢量化。Dreamy我可以像下面这样添加它们:

sum_v                                     //|  0  | x15 | x14 | x13 |...| x10 |...| x7 |...| x4 |...| x1 | 
sum_v1 = sum_v >> 1*16                    //|  0  |  0  | x15 | x14 |...| x11 |...| x8 |...| x5 |...| x2 |  
sum_v2 = sumv >> 2*16                     //|  0  |  0  |  0  | x15 |...| x12 |...| x9 |...| x6 |...| x3 |
result_vec = add_epi16 (sum_v,sum_v1,sum_v2)

//then I should extact the result_vec to outputs 
垂直添加它们将提供答案。 但不幸的是,AVX2没有256位的移位操作,而256位寄存器被视为两个128位通道。在这种情况下,我应该使用排列。但是我找不到一个合适的
permut
shuffle
,等等。是否有任何关于此实施的建议应尽可能快


使用
gcc
linuxmint
intrinsics
skylake
,您可以尝试类似的方法

__m256i idx1 = _mm256_setr_epi8(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1);
__m256i idx2 = _mm256_setr_epi32(1,2,3,4,5,6,7,0);

__m256i t1 = _mm256_shuffle_epi8 (t0, idx1);
__m256i t2 = _mm256_permute2x128_si256(t1, t1, 1);
__m256i t3 = _mm256_blend_epi16(t1,t2,0x80);
__m256i t4 = _mm256_permutevar8x32_epi32(t0, idx2);
__m256i s = _mm256_add_epi16(t0, _mm256_add_epi16(t3,t4));
我以这个例子为基础

下面是一个工作示例

#include <stdio.h>
#include <x86intrin.h>

int main(void) {
  short x[16];

  for(int i=0; i<16; i++) x[i] = i;
  __m256i idx1 = _mm256_setr_epi8(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1,
                  2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1);
  __m256i idx2 = _mm256_setr_epi32(1,2,3,4,5,6,7,0);

  __m256i t0 = _mm256_loadu_si256((__m256i*)x);
  __m256i t1 = _mm256_shuffle_epi8 (t0, idx1);
  __m256i t2 = _mm256_permute2x128_si256(t1, t1, 1);
  __m256i t3 = _mm256_blend_epi16(t1,t2,0x80);
  __m256i t4 = _mm256_permutevar8x32_epi32(t0, idx2);
  __m256i s = _mm256_add_epi16(t0, _mm256_add_epi16(t3,t4));

  short y[16];
  _mm256_storeu_si256((__m256i*)y, t0);
  for(int i=0; i<16; i++) printf("%2x ", y[i]); puts("");
  _mm256_storeu_si256((__m256i*)y, t3);
  for(int i=0; i<16; i++) printf("%2x ", y[i]); puts("");
  _mm256_storeu_si256((__m256i*)y, t4);
  for(int i=0; i<16; i++) printf("%2x ", y[i]); puts("");
  _mm256_storeu_si256((__m256i*)y, s);
  for(int i=0; i<16; i++) printf("%2x ", y[i]); puts("");
}

您可以尝试使用以下内容:

#include <immintrin.h>
#include <iostream>

template<class T> inline void Print(const __m256i & v)
{
    T b[sizeof(v) / sizeof(T)];
    _mm256_storeu_si256((__m256i*)b, v);
    for (int i = 0; i < sizeof(v) / sizeof(T); i++)
        std::cout << int(b[i]) << " ";
    std::cout << std::endl;
}

template<int shift> inline __m256i Shift(const __m256i & a)
{
    return _mm256_alignr_epi8(_mm256_permute2x128_si256(a, _mm256_setzero_si256(), 0x31), a, shift * 2);
}

int main()
{
    __m256i v0 = _mm256_setr_epi16(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, 0);
    __m256i v1 = Shift<1>(v0);
    __m256i v2 = Shift<2>(v0);
    __m256i r = _mm256_add_epi16(v0, _mm256_add_epi16(v1, v2));

    Print<short>(v0);
    Print<short>(v1);
    Print<short>(v2);
    Print<short>(r);
}

您可以通过两次加法和两次“洗牌”来完成此操作:_mm256_bsrli_epi128以零为单位进行移位 答案不感兴趣的位置。对于_mm256_permutevar8x32_epi32 我们选择了一个重复上32位的排列,但这些位也是相同的 与答案无关

#include <stdio.h>
#include <x86intrin.h>
/*  gcc -O3 -Wall -m64 -march=haswell hor_sum3x3.c   */
int print_vec_short(__m256i x);
int print_12_9_6_3_0_short(__m256i x);

int main() {
   short x[16];

   for(int i=0; i<16; i++) x[i] = i+1; x[15] = 0;

   __m256i t0   = _mm256_loadu_si256((__m256i*)x);                              


   __m256i t1   = _mm256_bsrli_epi128(t0,2);             /* Shift 128 bit lanes in t0 right by 2 bytes while shifting in zeros. Fortunately the zeros are in the positions that we don't need */ 
   __m256i t2   = _mm256_permutevar8x32_epi32(t0,_mm256_set_epi32(7,7,6,5,4,3,2,1)); /* Shift right by 4 bytes     */
   __m256i sum  = _mm256_add_epi16(_mm256_add_epi16(t0,t1),t2);

   printf("t0  = ");print_vec_short(t0);
   printf("t1  = ");print_vec_short(t1);
   printf("t2  = ");print_vec_short(t2);
   printf("sum = ");print_vec_short(sum);

   printf("\nvector elements of interest: columns 12, 9, 6, 3, 0:\n");
   printf("t0[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t0);
   printf("t1[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t1);
   printf("t2[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t2);
   printf("sum[12, 9, 6, 3, 0] = ");print_12_9_6_3_0_short(sum);
   return 0;
}


int print_vec_short(__m256i x){
   short int v[16];
   _mm256_storeu_si256((__m256i *)v,x);
   printf("%4hi %4hi %4hi %4hi | %4hi %4hi %4hi %4hi | %4hi %4hi %4hi %4hi  | %4hi %4hi %4hi %4hi \n",
          v[15],v[14],v[13],v[12],v[11],v[10],v[9],v[8],v[7],v[6],v[5],v[4],v[3],v[2],v[1],v[0]);
   return 0;
}

int print_12_9_6_3_0_short(__m256i x){
   short int v[16];
   _mm256_storeu_si256((__m256i *)v,x);
   printf("%4hi %4hi %4hi %4hi %4hi  \n",v[12],v[9],v[6],v[3],v[0]);
   return 0;
}

您是否关心潜在的溢出,即在添加之前解包到32位是否更好,或者这不是问题?@PaulR,每个16位元素包含一个0到255之间的数字作为像素。所以溢出可能已经饱和,但我不喜欢这个程序。你提到了一个很好的观点,我可以使用一些额外的和未使用的位来提高精度。我更喜欢你的解决方案,而不是地雷计数指令,你的解决方案在加法之前使用4,我的解决方案使用5。我不太熟悉
vpaligner
@Zboson,我最近发现了这条指令-它对于计算卷积非常有用。@Zboson A在加法之前只有2条指令也是可能的,因为我们不需要精确的256位移位。有些“漏洞”是允许的。原来我的原始答案是正确的,所以现在我的答案使用了与你相同数量的说明。当然,计数指令并不是性能的最终指标。看看哪种方法会赢,这会很有趣。无论如何,@wim现在似乎有了最好的解决方案。太棒了!但第二列是错误的。应该是15而不是30。我原来有4个指令,但后来不得不添加一个来修复它。还有一点值得考虑,我不确定OP是否意味着我们可以假设最后一个元素为零。也许OP意味着最后一个元素不应该在加法中使用。我不知道。我的意思是,如果你把t0的最后一个元素设为16,会发生什么?这会传播到和吗?如果是这样的话,我想那不是OP想要的。我可能犯了一个错误,我做了
x[15]=0
。是的,我刚刚删除了
x[15]=0
,总和的最后三个元素变成了
324645
,但它们应该是
01529
@Zboson你的意思是
sum=034242.
行中的
30
是错误的吗?就我所理解的问题而言,这个
30
对OP不感兴趣。output1…output5是列0,36,9,12,从右边开始计算?@Zboson我认为我们不必担心
x[15]
的值,因为
sum[12,9,6,3,0]=42 33 24 15 6
x[15]的值无关
@wim,实际上我可以用更简单的方式处理输出。我认为这将产生另一个巨大的加速,我必须将结果隐藏在一个向量中。该程序有3个步骤。这五个结果适用于步骤1。步骤2将计算另外5个结果,步骤3将计算另外5个结果。它将产生15个结果,可以存储在一个对齐的地址,但我应该发挥它。这些结果是针对这些位置的:步骤1:{12,9,6,3,0},步骤2:{13,10,7,4,1}步骤3:{14,11,8,5,2}最后一个包含未对齐存储的{14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}的向量将完成此任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0
2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 0
3 4 5 6 7 8 9 10 11 12 13 14 15 0 0 0
6 9 12 15 18 21 24 27 30 33 36 39 42 29 15 0
#include <stdio.h>
#include <x86intrin.h>
/*  gcc -O3 -Wall -m64 -march=haswell hor_sum3x3.c   */
int print_vec_short(__m256i x);
int print_12_9_6_3_0_short(__m256i x);

int main() {
   short x[16];

   for(int i=0; i<16; i++) x[i] = i+1; x[15] = 0;

   __m256i t0   = _mm256_loadu_si256((__m256i*)x);                              


   __m256i t1   = _mm256_bsrli_epi128(t0,2);             /* Shift 128 bit lanes in t0 right by 2 bytes while shifting in zeros. Fortunately the zeros are in the positions that we don't need */ 
   __m256i t2   = _mm256_permutevar8x32_epi32(t0,_mm256_set_epi32(7,7,6,5,4,3,2,1)); /* Shift right by 4 bytes     */
   __m256i sum  = _mm256_add_epi16(_mm256_add_epi16(t0,t1),t2);

   printf("t0  = ");print_vec_short(t0);
   printf("t1  = ");print_vec_short(t1);
   printf("t2  = ");print_vec_short(t2);
   printf("sum = ");print_vec_short(sum);

   printf("\nvector elements of interest: columns 12, 9, 6, 3, 0:\n");
   printf("t0[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t0);
   printf("t1[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t1);
   printf("t2[12, 9, 6, 3, 0]  = ");print_12_9_6_3_0_short(t2);
   printf("sum[12, 9, 6, 3, 0] = ");print_12_9_6_3_0_short(sum);
   return 0;
}


int print_vec_short(__m256i x){
   short int v[16];
   _mm256_storeu_si256((__m256i *)v,x);
   printf("%4hi %4hi %4hi %4hi | %4hi %4hi %4hi %4hi | %4hi %4hi %4hi %4hi  | %4hi %4hi %4hi %4hi \n",
          v[15],v[14],v[13],v[12],v[11],v[10],v[9],v[8],v[7],v[6],v[5],v[4],v[3],v[2],v[1],v[0]);
   return 0;
}

int print_12_9_6_3_0_short(__m256i x){
   short int v[16];
   _mm256_storeu_si256((__m256i *)v,x);
   printf("%4hi %4hi %4hi %4hi %4hi  \n",v[12],v[9],v[6],v[3],v[0]);
   return 0;
}
$ ./a.out
t0  =    0   15   14   13 |   12   11   10    9 |    8    7    6    5  |    4    3    2    1 
t1  =    0    0   15   14 |   13   12   11   10 |    0    8    7    6  |    5    4    3    2 
t2  =    0   15    0   15 |   14   13   12   11 |   10    9    8    7  |    6    5    4    3 
sum =    0   30   29   42 |   39   36   33   30 |   18   24   21   18  |   15   12    9    6 

vector elements of interest: columns 12, 9, 6, 3, 0:
t0[12, 9, 6, 3, 0]  =   13   10    7    4    1  
t1[12, 9, 6, 3, 0]  =   14   11    8    5    2  
t2[12, 9, 6, 3, 0]  =   15   12    9    6    3  
sum[12, 9, 6, 3, 0] =   42   33   24   15    6