jjzjj

c++ - 快速加权均值和方差10格

coder 2024-02-08 原文

我想加快我的代码的一部分,但我认为没有一种更好的方法可以进行以下计算:

float invSum = 1.0f / float(sum);

for (int i = 0; i < numBins; ++i)
{
    histVec[i] *= invSum;
}

for (int i = 0; i < numBins; ++i)
{
    float midPoint = (float)i*binSize + binOffset;
    float f = histVec[i];
    fmean += f * midPoint;
}

for (int i = 0; i < numBins; ++i)
{
    float midPoint = (float)i*binSize + binOffset;
    float f = histVec[i];
    float diff = midPoint - fmean;
    var += f * hwk::sqr(diff);
}

for循环中的numBins通常为10,但是经常会调用此位(频率为每秒80帧,每帧至少被调用8次)

我尝试使用一些SSE方法,但这只是稍微加快了这段代码的速度。我想我可以避免两次计算midPoint,但是我不确定如何计算。有没有更好的方法来计算fmean和var?

这是SSE代码:
// make hist contain a multiple of 4 valid values
    for (int i = numBins; i < ((numBins + 3) & ~3); i++) 
        hist[i] = 0;

// find sum of bins in inHist
    __m128i iSum4 = _mm_set1_epi32(0);
    for (int i = 0; i < numBins; i += 4) 
    {
        __m128i a = *((__m128i *) &inHist[i]);
        iSum4 = _mm_add_epi32(iSum4, a);
    }

    int iSum = iSum4.m128i_i32[0] + iSum4.m128i_i32[1] + iSum4.m128i_i32[2] + iSum4.m128i_i32[3];

    //float stdevB, meanB;

    if (iSum == 0.0f)
    {
        stdev = 0.0;
        mean = 0.0;
    }
    else
    {   
        // Set histVec to normalised values in inHist
        __m128 invSum = _mm_set1_ps(1.0f / float(iSum));
        for (int i = 0; i < numBins; i += 4) 
        {
            __m128i a = *((__m128i *) &inHist[i]);
            __m128  b = _mm_cvtepi32_ps(a);
            __m128  c = _mm_mul_ps(b, invSum);
            _mm_store_ps(&histVec[i], c);
        }

        float binSize = 256.0f / (float)numBins;
        float halfBinSize = binSize * 0.5f;
        float binOffset = halfBinSize;

        __m128 binSizeMask = _mm_set1_ps(binSize);
        __m128 binOffsetMask = _mm_set1_ps(binOffset);
        __m128 fmean4 = _mm_set1_ps(0.0f);
        for (int i = 0; i < numBins; i += 4)
        {
            __m128i idx4 = _mm_set_epi32(i + 3, i + 2, i + 1, i);
            __m128  idx_m128 = _mm_cvtepi32_ps(idx4);
            __m128  histVec4 = _mm_load_ps(&histVec[i]);
            __m128  midPoint4 = _mm_add_ps(_mm_mul_ps(idx_m128, binSizeMask), binOffsetMask);
            fmean4 = _mm_add_ps(fmean4, _mm_mul_ps(histVec4, midPoint4));
        }
        fmean4 = _mm_hadd_ps(fmean4, fmean4); // 01 23 01 23
        fmean4 = _mm_hadd_ps(fmean4, fmean4); // 0123 0123 0123 0123

        float fmean = fmean4.m128_f32[0]; 

        //fmean4 = _mm_set1_ps(fmean);
        __m128 var4 = _mm_set1_ps(0.0f);
        for (int i = 0; i < numBins; i+=4)
        {
            __m128i idx4 = _mm_set_epi32(i + 3, i + 2, i + 1, i);
            __m128  idx_m128 = _mm_cvtepi32_ps(idx4);
            __m128  histVec4 = _mm_load_ps(&histVec[i]);
            __m128  midPoint4 = _mm_add_ps(_mm_mul_ps(idx_m128, binSizeMask), binOffsetMask);
            __m128  diff4 = _mm_sub_ps(midPoint4, fmean4);
            var4 = _mm_add_ps(var4, _mm_mul_ps(histVec4, _mm_mul_ps(diff4, diff4)));
        }

        var4 = _mm_hadd_ps(var4, var4); // 01 23 01 23
        var4 = _mm_hadd_ps(var4, var4); // 0123 0123 0123 0123
        float var = var4.m128_f32[0]; 

        stdev = sqrt(var);
        mean = fmean;
    }

我可能做错了,因为我没有得到很多期望的改进。
SSE代码中是否有可能减慢该过程的速度?

(编者注:此问题的SSE部分最初被要求为https://stackoverflow.com/questions/31837817/foor-loop-optimisation-sse-comparison,它作为重复项被关闭。)

最佳答案

我只是意识到您的数据数组以int数组开始,因为您的代码中没有声明。我可以看到在SSE版本中,您以整数开头,并且以后只存储它的浮点版本。

保持所有整数为整数,将使我们可以使用简单的ivec = _mm_add_epi32(ivec, _mm_set1_epi32(4));来完成循环计数器 vector 。Aki Suihkonen的答案进行了一些转换,应使其更优化。特别是,即使没有-ffast-math,自动矢量化程序也应该能够执行更多操作。实际上,它做得很好。您可以使用内在函数做得更好,尤其是。节省一些 vector 32位乘法并缩短依赖链。

我的旧答案是基于尝试优化编写的代码(假设FP输入):

您可以使用the algorithm @Jason linked to将所有3个循环组合为一个循环。但是,由于涉及部门,因此它可能没有利润。对于少量的垃圾箱,可能只是循环多次。

首先阅读http://agner.org/optimize/上的指南。他的“优化装配体”指南中的几种技术可以加快您的SSE尝试速度(我已为您编辑了此问题)。

  • 在可能的情况下结合您的循环,因此,每次加载/存储数据时,您都会对数据做更多的工作。
  • 多个累加器可隐藏循环携带的依赖链的延迟。 (即使是FP add,在最新的Intel CPU上也要花费3个周期。)这不适用于像您的情况那样短的阵列。
  • 而不是每次迭代都进行int-> float转换,请使用float循环计数器以及int循环计数器。 (在每次迭代中添加一个_mm_set1_ps(4.0f) vector 。)在可能的情况下,应避免在循环中使用带有args变量的_mm_set...。它需要几个指令(尤其是当必须分别计算setr的每个参数时。)

  • gcc -O3设法自动向量化第一个循环,但不能自动向量化。使用-O3 -ffast-math,它可以自动向量化更多内容。 -ffast-math允许它以与代码指定顺序不同的顺序执行FP操作。例如将数组与 vector 的4个元素相加,最后只组合4个累加器。

    告诉gcc输入指针是16对齐的,这样gcc可以自动向量化,而开销却少得多(未对齐部分没有标量循环)。
    // return mean
    float fpstats(float histVec[], float sum, float binSize, float binOffset, long numBins, float *variance_p)
    {
        numBins += 3;
        numBins &= ~3;  // round up to multiple of 4.  This is just a quick hack to make the code fast and simple.
        histVec = (float*)__builtin_assume_aligned(histVec, 16);
    
        float invSum = 1.0f / float(sum);
        float var = 0, fmean = 0;
    
        for (int i = 0; i < numBins; ++i)
        {
            histVec[i] *= invSum;
            float midPoint = (float)i*binSize + binOffset;
            float f = histVec[i];
            fmean += f * midPoint;
        }
    
        for (int i = 0; i < numBins; ++i)
        {
            float midPoint = (float)i*binSize + binOffset;
            float f = histVec[i];
            float diff = midPoint - fmean;
    //        var += f * hwk::sqr(diff);
            var += f * (diff * diff);
        }
        *variance_p = var;
        return fmean;
    }
    

    gcc为第二个循环生成一些奇怪的代码。
            # broadcasting fmean after the 1st loop
            subss   %xmm0, %xmm2    # fmean, D.2466
            shufps  $0, %xmm2, %xmm2        # vect_cst_.16
    .L5: ## top of 2nd loop 
            movdqa  %xmm3, %xmm5    # vect_vec_iv_.8, vect_vec_iv_.8
            cvtdq2ps        %xmm3, %xmm3    # vect_vec_iv_.8, vect__32.9
            movq    %rcx, %rsi      # D.2465, D.2467
            addq    $1, %rcx        #, D.2465
            mulps   %xmm1, %xmm3    # vect_cst_.11, vect__33.10
            salq    $4, %rsi        #, D.2467
            paddd   %xmm7, %xmm5    # vect_cst_.7, vect_vec_iv_.8
            addps   %xmm2, %xmm3    # vect_cst_.16, vect_diff_39.15
            mulps   %xmm3, %xmm3    # vect_diff_39.15, vect_powmult_53.17
            mulps   (%rdi,%rsi), %xmm3      # MEM[base: histVec_10, index: _107, offset: 0B], vect__41.18
            addps   %xmm3, %xmm4    # vect__41.18, vect_var_42.19
            cmpq    %rcx, %rax      # D.2465, bnd.26
            ja      .L8     #,   ### <--- This is insane.
            haddps  %xmm4, %xmm4    # vect_var_42.19, tmp160
            haddps  %xmm4, %xmm4    # tmp160, vect_var_42.21
    .L2:
            movss   %xmm4, (%rdx)   # var, *variance_p_44(D)
            ret
            .p2align 4,,10
            .p2align 3
    .L8:
            movdqa  %xmm5, %xmm3    # vect_vec_iv_.8, vect_vec_iv_.8
            jmp     .L5     #
    

    因此,gcc决定跳到复制寄存器,然后无条件地将jmp返回循环的顶部,而不是每次迭代都跳回顶部。 uop循环缓冲区可能会消除这种愚蠢的前端开销,但是gcc应该已经构造了循环,因此它不会在每次迭代时都复制xmm5-> xmm3,然后再复制xmm3-> xmm5,因为这很愚蠢。它应该具有条件跳转,只需转到循环的顶部即可。

    还要注意gcc用于获取循环计数器的浮点版本的技术:从1 2 3 4的整数 vector 开始,然后添加set1_epi32(4)。将其用作打包的int-> float cvtdq2ps的输入。在Intel HW上,该指令在FP-add端口上运行,并具有3个周期的延迟,与打包的FP add相同。海湾合作委员会的概率。最好添加一个set1_ps(4.0) vector ,即使这样做会创建一个3循环的循环依赖链,而不是1循环的 vector int add,并且每次迭代都会产生3循环的转换。

    迭代次数少

    您说这通常会在10个垃圾箱上使用吗?仅10个bin的专用版本可以避免所有循环开销并将所有内容保留在寄存器中,从而可以大大提高速度。

    这么小的问题大小,您就可以将FP权重放在内存中,而不必每次都用integer-> float转换重新计算它们。

    同样,相对于垂直操作的数量,10个仓位将意味着很多水平操作,因为您只有2个半 vector 的数据。

    如果确实确实有10个是常见的,请为此专门设计一个版本。如果16岁以下的 child 很普遍,请为此专门制作一个版本。 (他们可以并且应该共享const float weights[] = { 0.0f, 1.0f, 2.0f, ...};数组。)

    您可能希望将内在函数用于特殊的小问题版本,而不是自动向量化。

    在您的专用版本中,在数组中的有用数据结束后进行零填充可能仍然是一个好主意。但是,您可以加载最后2个浮点,并使用movq指令清除 vector 寄存器的高64b。 (__m128i _mm_cvtsi64_si128 (__int64 a))。将此转换为__m128,就可以了。

    关于c++ - 快速加权均值和方差10格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31833965/

    有关c++ - 快速加权均值和方差10格的更多相关文章

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

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

    2. 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.你能做的最好的事情是:

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

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

    4. 由于 libgmp.10.dylib 的问题,Ruby 2.2.0 无法运行 - 2

      我刚刚安装了带有RVM的Ruby2.2.0,并尝试使用它得到了这个:$rvmuse2.2.0--defaultUsing/Users/brandon/.rvm/gems/ruby-2.2.0dyld:Librarynotloaded:/usr/local/lib/libgmp.10.dylibReferencedfrom:/Users/brandon/.rvm/rubies/ruby-2.2.0/bin/rubyReason:Incompatiblelibraryversion:rubyrequiresversion13.0.0orlater,butlibgmp.10.dylibpro

    5. 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”]、[“苹果”、“

    6. ruby - ri 有空文件 – Ubuntu 11.10, Ruby 1.9 - 2

      我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da

    7. += 的 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=

    8. ruby-on-rails - gem install rmagick -v 2.13.1 错误 Failed to build gem native extension on Mac OS 10.9.1 - 2

      我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/

    9. ruby - 如何以表格格式快速打印 Ruby 哈希值? - 2

      有没有办法快速将表格格式的ruby​​哈希打印到文件中?如:keyAkeyBkeyC...1232343451253474456...其中散列的值是不同大小的数组。还是使用双循环是唯一的方法?谢谢 最佳答案 试试我写的这个gem(在表中打印散列、ruby对象、ActiveRecord对象):http://github.com/arches/table_print 关于ruby-如何以表格格式快速打印Ruby哈希值?,我们在StackOverflow上找到一个类似的问题:

    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

    随机推荐