草庐IT

c++ - AVX2 根据条件将连续元素扩展为稀疏 vector ? (如 AVX512 VPEXPANDD)

coder 2024-02-23 原文

有谁知道如何向量化以下代码?

uint32_t r[8];
uint16_t* ptr;
for (int j = 0; j < 8; ++j)
    if (r[j] < C)
        r[j] = *(ptr++);

这基本上是一个屏蔽的收集操作。自动矢量化器无法处理这个问题。如果 ptr 是一个 uint32_t* 它应该可以直接用 _mm256_mask_i32gather_epi32 实现.但即便如此,你如何生成正确的索引 vector ?并且无论如何只使用打包加载并洗牌结果(需要类似的索引 vector )会不会更快?

最佳答案

更新答案:主要代码段已重写为函数和解决方案 添加了适用于 AMD 处理器的内容。

正如 Peter Cordes 在评论中提到的,AVX-512 指令 vpexpandd在这里会有用。 函数 _mm256_mask_expand_epi32_AVX2_BMI()_mm256_mask_expand_epi32_AVX2()下面更多 或更少地模仿这条指令。 AVX2_BMI 变体适用于 Intel Haswell 处理器和更新版本。 _mm256_mask_expand_epi32_AVX2()功能适用于速度较慢或速度较慢的 AMD 处理器 缺少pdep指令,例如 Ryzen 处理器。 在这个函数中有一些高吞吐量的指令, 例如移位和简单的算术运算,被用来代替 pdep操作说明。 AMD 处理器的另一种可能性是 一次只处理 4 个元素,并使用一个很小的(16 个元素)查找表来检索 shuf_mask。

在这两个函数下面显示了如何使用它们来矢量化您的标量代码

答案使用与 this 中类似的想法Peter Cordes 的回答, 其中讨论了基于掩码的左包装。在那个答案中 BMI2 使用说明 pext用于计算置换 vector 。 这里我们使用 pdep相反,指令计算置换 vector 。 函数_mm256_mask_expand_epi32_AVX2()找到置换 vector 以不同的方式通过计算 一个prefix sumr<C 上面具。

因为未签名的 uint32_t ,我将 Paul R 的想法用于 epu32无符号比较。

/*     gcc -O3 -m64 -Wall -mavx2 -march=broadwell mask_expand_avx.c     */
#include <immintrin.h>
#include <stdio.h>
#include <stdint.h>

__m256i _mm256_mask_expand_epi32_AVX2_BMI(__m256i src, __m256i mask, __m256i insert_vals, int* nonz){ 
    /* Scatter the insert_vals to the positions indicated by mask.                                                                    */               
    /* Blend the src with these scattered insert_vals.                                                                                */
    /* Return also the number of nonzeros in mask (which is inexpensive here                                                          */
    /* because _mm256_movemask_epi8(mask) has to be computed anyway.)                                                                          */
    /* This code is suitable for Intel Haswell and newer processors.                                                                  */
    /* This code is less suitble for AMD Ryzen processors, due to the                                                                 */
    /* slow pdep instruction on those processors, see _mm256_mask_expand_epi32_AVX2                                                   */
    uint32_t all_indx         = 0x76543210;
    uint32_t mask_int32       = _mm256_movemask_epi8(mask);                           /* Packed mask of 8 nibbles                     */
    uint32_t wanted_indx      = _pdep_u32(all_indx, mask_int32);                      /* Select the right nibbles from all_indx       */
    uint64_t expand_indx      = _pdep_u64(wanted_indx, 0x0F0F0F0F0F0F0F0F);           /* Expand the nibbles to bytes                  */
    __m128i  shuf_mask_8bit   = _mm_cvtsi64_si128(expand_indx);                       /* Move to AVX-128 register                     */
    __m256i  shuf_mask        = _mm256_cvtepu8_epi32(shuf_mask_8bit);                 /* Expand bytes to 32-bit integers              */
    __m256i  insert_vals_exp  = _mm256_permutevar8x32_epi32(insert_vals, shuf_mask);  /* Expand insert_vals to the right positions    */
    __m256i  dst              = _mm256_blendv_epi8(src, insert_vals_exp, mask);       /* src is replaced by insert_vals_exp at the postions indicated by mask */
             *nonz            = _mm_popcnt_u32(mask_int32) >> 2;
             return dst;
}


__m256i _mm256_mask_expand_epi32_AVX2(__m256i src, __m256i mask, __m256i insert_vals, int* nonz){ 
    /* Scatter the insert_vals to the positions indicated by mask.                                                                    */               
    /* Blend the src with these scattered insert_vals.                                                                                */
    /* Return also the number of nonzeros in mask.                                                                                    */
    /* This code is an alternative for the _mm256_mask_expand_epi32_AVX2_BMI function.                                                */
    /* In contrast to that code, this code doesn't use the BMI instruction pdep.                                                      */
    /* Therefore, this code is suitable for AMD processors.                                                                            */
    __m128i  mask_lo          = _mm256_castsi256_si128(mask);                      
    __m128i  mask_hi          = _mm256_extracti128_si256(mask, 1);                  
    __m128i  mask_hi_lo       = _mm_packs_epi32(mask_lo, mask_hi);                    /* Compressed 128-bits (8 x 16-bits) mask       */
             *nonz            = _mm_popcnt_u32(_mm_movemask_epi8(mask_hi_lo)) >> 1;
    __m128i  prefix_sum       = mask_hi_lo;
    __m128i  prefix_sum_shft  = _mm_slli_si128(prefix_sum, 2);                        /* The permutation vector is based on the       */
             prefix_sum       = _mm_add_epi16(prefix_sum, prefix_sum_shft);           /* Prefix sum of the mask.                      */
             prefix_sum_shft  = _mm_slli_si128(prefix_sum, 4);
             prefix_sum       = _mm_add_epi16(prefix_sum, prefix_sum_shft);
             prefix_sum_shft  = _mm_slli_si128(prefix_sum, 8);
             prefix_sum       = _mm_add_epi16(prefix_sum, prefix_sum_shft);
    __m128i  shuf_mask_16bit  = _mm_sub_epi16(_mm_set1_epi16(-1), prefix_sum);
    __m256i  shuf_mask        = _mm256_cvtepu16_epi32(shuf_mask_16bit);               /* Expand 16-bit integers to 32-bit integers    */
    __m256i  insert_vals_exp  = _mm256_permutevar8x32_epi32(insert_vals, shuf_mask);  /* Expand insert_vals to the right positions    */
    __m256i  dst              = _mm256_blendv_epi8(src, insert_vals_exp, mask);       /* src is replaced by insert_vals_exp at the postions indicated by mask */
             return dst;
}


/* Unsigned integer compare _mm256_cmplt_epu32 doesn't exist                                                    */
/* The next two lines are based on Paul R's answer https://stackoverflow.com/a/32945715/2439725                 */
#define _mm256_cmpge_epu32(a, b) _mm256_cmpeq_epi32(_mm256_max_epu32(a, b), a)
#define _mm256_cmplt_epu32(a, b) _mm256_xor_si256(_mm256_cmpge_epu32(a, b), _mm256_set1_epi32(-1))

int print_input(uint32_t* r, uint32_t C, uint16_t* ptr);
int print_output(uint32_t* r, uint16_t* ptr);

int main(){
    int       nonz;
    uint32_t  r[8]        = {6, 3, 1001, 2, 1002, 7, 5, 1003};
    uint32_t  r_new[8];
    uint32_t  C           = 9;
    uint16_t* ptr         = malloc(8*2);  /* allocate 16 bytes for 8 uint16_t's */
              ptr[0] = 11; ptr[1] = 12; ptr[2] = 13;ptr[3] = 14; ptr[4] = 15; ptr[5] = 16; ptr[6] = 17; ptr[7] = 18;
    uint16_t* ptr_new;

              printf("Test values:\n");
              print_input(r,C,ptr);

    __m256i   src         = _mm256_loadu_si256((__m256i *)r);
    __m128i   ins         = _mm_loadu_si128((__m128i *)ptr);
    __m256i   insert_vals = _mm256_cvtepu16_epi32(ins);
    __m256i   mask_C      = _mm256_cmplt_epu32(src,_mm256_set1_epi32(C));   


              printf("Output _mm256_mask_expand_epi32_AVX2_BMI:\n");
    __m256i   output      = _mm256_mask_expand_epi32_AVX2_BMI(src, mask_C, insert_vals, &nonz);
                            _mm256_storeu_si256((__m256i *)r_new,output);
              ptr_new     = ptr + nonz;
              print_output(r_new,ptr_new);              


              printf("Output _mm256_mask_expand_epi32_AVX2:\n");
              output      = _mm256_mask_expand_epi32_AVX2(src, mask_C, insert_vals, &nonz);
                            _mm256_storeu_si256((__m256i *)r_new,output);
              ptr_new     = ptr + nonz;
              print_output(r_new,ptr_new);              


              printf("Output scalar loop:\n");
              for (int j = 0; j < 8; ++j)
                  if (r[j] < C)
                      r[j] = *(ptr++);
              print_output(r,ptr);              

              return 0;
}

int print_input(uint32_t* r, uint32_t C, uint16_t* ptr){
    printf("r[0]..r[7]        =     %4u  %4u  %4u  %4u  %4u  %4u  %4u  %4u  \n",r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7]);
    printf("Threshold value C =     %4u  %4u  %4u  %4u  %4u  %4u  %4u  %4u  \n",C,C,C,C,C,C,C,C);
    printf("ptr[0]..ptr[7]    =     %4hu  %4hu  %4hu  %4hu  %4hu  %4hu  %4hu  %4hu  \n\n",ptr[0],ptr[1],ptr[2],ptr[3],ptr[4],ptr[5],ptr[6],ptr[7]);
    return 0;
}

int print_output(uint32_t* r, uint16_t* ptr){
    printf("r[0]..r[7]        =     %4u  %4u  %4u  %4u  %4u  %4u  %4u  %4u  \n",r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7]);
    printf("ptr               = %p \n\n",ptr);
    return 0;
}

输出是:

$ ./a.out
Test values:
r[0]..r[7]        =        6     3  1001     2  1002     7     5  1003  
Threshold value C =        9     9     9     9     9     9     9     9  
ptr[0]..ptr[7]    =       11    12    13    14    15    16    17    18  

Output _mm256_mask_expand_epi32_AVX2_BMI:
r[0]..r[7]        =       11    12  1001    13  1002    14    15  1003  
ptr               = 0x92c01a 

Output _mm256_mask_expand_epi32_AVX2:
r[0]..r[7]        =       11    12  1001    13  1002    14    15  1003  
ptr               = 0x92c01a 

Output scalar loop:
r[0]..r[7]        =       11    12  1001    13  1002    14    15  1003  
ptr               = 0x92c01a 

关于c++ - AVX2 根据条件将连续元素扩展为稀疏 vector ? (如 AVX512 VPEXPANDD),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48174640/

有关c++ - AVX2 根据条件将连续元素扩展为稀疏 vector ? (如 AVX512 VPEXPANDD)的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  4. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  5. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  6. ruby - 如何根据长度将路径数组转换为嵌套数组或散列 - 2

    我需要根据字符串路径的长度将字符串路径数组转换为符号、哈希和数组的数组给定以下数组:array=["info","services","about/company","about/history/part1","about/history/part2"]我想生成以下输出,对不同级别进行分组,根据级别的结构混合使用符号和对象。产生以下输出:[:info,:services,about:[:company,history:[:part1,:part2]]]#altsyntax[:info,:services,{:about=>[:company,{:history=>[:part1,:pa

  7. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  8. += 的 Ruby 方法 - 2

    有没有办法让Ruby能够做这样的事情?classPlane@moved=0@x=0defx+=(v)#thisiserror@x+=v@moved+=1enddefto_s"moved#{@moved}times,currentxis#{@x}"endendplane=Plane.newplane.x+=5plane.x+=10putsplane.to_s#moved2times,currentxis15 最佳答案 您不能在Ruby中覆盖复合赋值运算符。任务在内部处理。您应该覆盖+,而不是+=。plane.a+=b与plane.a=

  9. ruby - 尝试比较两个文本文件,并根据信息创建第三个 - 2

    我有两个文本文件,master.txt和926.txt。如果926.txt中有一行不在master.txt中,我想写入一个新文件notinbook.txt。我写了我能想到的最好的东西,但考虑到我是一个糟糕的/新手程序员,它失败了。这是我的东西g=File.new("notinbook.txt","w")File.open("926.txt","r")do|f|while(line=f.gets)x=line.chompifFile.open("master.txt","w")do|h|endwhile(line=h.gets)ifline.chomp!=xputslineendende

  10. ruby - Sinatra + Heroku + Datamapper 使用 dm-sqlite-adapter 部署问题 - 2

    出于某种原因,heroku尝试要求dm-sqlite-adapter,即使它应该在这里使用Postgres。请注意,这发生在我打开任何URL时-而不是在gitpush本身期间。我构建了一个默认的Facebook应用程序。gem文件:source:gemcuttergem"foreman"gem"sinatra"gem"mogli"gem"json"gem"httparty"gem"thin"gem"data_mapper"gem"heroku"group:productiondogem"pg"gem"dm-postgres-adapter"endgroup:development,:t

随机推荐