【问题标题】:How to add an AVX2 vector horizontally 3 by 3?如何水平添加 3 x 3 的 AVX2 向量?
【发布时间】:2017-06-25 18:57:14
【问题描述】:

我有一个包含 16x16 位元素的 __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

因为这部分放在我程序的bottleneck部分,所以我决定向量化是使用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、linux mint、intrinsics、skylake。

【问题讨论】:

  • 你是否关心潜在的溢出,即在添加之前解压到 32 位会更好,还是这不是问题?
  • @PaulR,每个 16 位元素都包含一个 0 到 255 之间的数字作为像素。所以溢出可能已经饱和,但我不关心这个程序。您提到了一个很好的观点,我可以使用一些额外的和未使用的位来提高准确性。

标签: c x86 simd intrinsics avx2


【解决方案1】:

你可以试试这样的

__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));

这个例子是基于this question。

这是一个工作示例

#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("");
}

输出

0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f 
1  2  3  4  5  6  7  8  9  a  b  c  d  e  f  0 
2  3  4  5  6  7  8  9  a  b  c  d  e  f  0  1 
3  6  9  c  f 12 15 18 1b 1e 21 24 27 2a 1d 10 

【讨论】:

    【解决方案2】:

    你可以尝试使用这样的东西:

    #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);
    }
    

    输出:

    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
    

    【讨论】:

    • 我比我更喜欢你的解决方案
    • 计数说明,你的解决方案在添加之前使用4,我的使用5。我对vpalignr不太熟悉。
    • @Zboson 我最近发现了这条指令——它对于计算卷积非常有用。
    • @Zboson A solution 在添加之前只有 2 条指令也是可能的,因为我们不需要精确的 256 位移位。一些“洞”是允许的。
    • 原来我的原始答案是正确的,所以现在我的答案使用了与您相同数量的说明。当然,计数指令并不是性能的最终指标。看看哪种方法获胜会很有趣。无论如何,@wim 现在似乎有最好的解决方案。
    【解决方案3】:

    您可以通过两次加法和只有 2 次“洗牌”来做到这一点:_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;
    }
    

    输出是:

    $ ./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  
    

    【讨论】:

    • 太棒了!但是第二列是错误的。它应该是 15 而不是 30。我原来有 4 条指令,但后来不得不添加一条来解决这个问题。还有一点值得考虑,我不确定 OP 是否意味着我们可以假设最后一个元素为零。也许 OP 意味着不应在添加中使用最后一个元素。我不知道。我的意思是如果将 t0 的最后一个元素设置为 16 会发生什么情况,这会传播到总和吗?如果是这样,那不是我认为的 OP 想要的。 x[15] = 0 可能我犯了一个错误。
    • 是的,我刚刚删除了x[15] = 0,总和的最后三个元素变成了32 46 45,但它们应该是0 15 29。
    • @Zboson 您的意思是sum = 0 30 29 42 | 39 ... 行中的30 是错误的吗?据我了解,这个30 对OP 不感兴趣。 output1...output5 是 0,3 6,9,12 列,从右数?
    • @Zboson 我认为我们不必担心x[15] 的值,因为sum[12, 9, 6, 3, 0] = 42 33 24 15 6 与x[15] 的值无关。
    • @wim,实际上我可以以更 SIMD 的方式处理输出。我认为它会产生另一个很大的加速,我必须将结果隐藏在一个向量中。该程序有 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} 的向量将完成这项工作。
    猜你喜欢
    • 1970-01-01
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多