代码之家  ›  专栏  ›  技术社区  ›  uray

优化,分支消除

  •  4
  • uray  · 技术社区  · 16 年前
    float mixValue = ... //in range -1.0f to 1.0f
    for(... ; ... ; ...  ) //long loop
    {
        float inputLevel = ... //in range -1.0f to 1.0f
        if(inputLevel < 0.0 && mixValue < 0.0)
        {
            mixValue = (mixValue + inputLevel) + (mixValue*inputLevel);
        }
        else
        {
            mixValue = (mixValue + inputLevel) - (mixValue*inputLevel);
        }
    }
    

    只是一个简单的问题,我们能计算一下吗 mixValue 无分支 ?或者其他优化建议,比如使用simd?

    编辑: 只是为了更多的信息,我最终 根据所选答案,使用此解决方案:

    const float sign[] = {-1, 1};
    float mixValue = ... //in range -1.0f to 1.0f
    for(... ; ... ; ...  ) //long loop
    {
        float inputLevel = ... //in range -1.0f to 1.0f
        unsigned a = *(unsigned*)(&mixValue);
        unsigned b = *(unsigned*)(&inputLevel);
    
        float mulValue = mixValue * inputLevel * sign[(a & b) >> (8*sizeof(unsigned)-1)];
        float addValue = mixValue + inputLevel;
        mixValue = addValue + mulValue;
    }
    

    谢谢您。

    8 回复  |  直到 8 年前
        1
  •  1
  •   Pedro d'Aquino    16 年前

    受到Roku答案的启发(在MSVC++10分支上),这似乎不是分支:

    #include <iostream>
    
    using namespace std;
    const float sign[] = {-1, 1};
    int main() {
        const int N = 10;
        float mixValue = -0.5F;
        for(int i = 0; i < N; i++) {
            volatile float inputLevel = -0.3F;
            int bothNegative = ((((unsigned char*)&inputLevel)[3] & 0x80) & (((unsigned char*)&mixValue)[3] & 0x80)) >> 7;
            mixValue = (mixValue + inputLevel) + (sign[bothNegative]*mixValue*inputLevel);
        }
    
        std::cout << mixValue << std::endl;
    }
    

    以下是由IDA Pro分析的反汇编(在MSVC++10上编译,发布模式):

    Disassembly http://img248.imageshack.us/img248/6865/floattestbranchmine.png

        2
  •  4
  •   user362638    16 年前

    这个怎么样?

    const float sign[] = {-1, 1};
    
    float mixValue = ... //in range -1.0f to 1.0f
    for(... ; ... ; ...  ) //long loop
    {
        float inputLevel = ... //in range -1.0f to 1.0f
        int bothNegative = (inputLevel < 0.0) & (mixValue < 0.0);
        mixValue = (mixValue + inputLevel) + (sign[bothNegative]*mixValue*inputLevel);
    }
    

    编辑: Mike是正确的,他将引入一个分支,并感谢Pedro证明了这一点。我将&改为&现在GCC(版本4.4.0)生成了无分支代码。

        3
  •  1
  •   uray    16 年前
    float mixValue = ... //in range -1.0f to 1.0f
    for(... ; ... ; ...  ) //long loop
    {
         float inputLevel = ... //in range -1.0f to 1.0f
         float mulValue = mixValue * inputLevel;
         float addValue = mixValue + inputLevel;
         __int32 a = *(__int32*)(&mixValue);
         __int32 b = *(__int32*)(&inputLevel);
         __int32 c = *(__int32*)(&mulValue);
         __int32 d = c & ((a ^ b) | 0x7FFFFFFF);
         mixValue = addValue + *(float*)(&d);
    }
    
        4
  •  0
  •   Anthony DiSanti    16 年前

    就在我头顶上(我确信它可以减少):

    mixValue = (mixValue + inputLevel) + (((mixValue / fabs(mixValue)) + (inputLevel / fabs(inputLevel))+1) / fabs(((mixValue / fabs(mixValue)) + (inputLevel / fabs(inputLevel))+1)))*-1*(mixValue*inputLevel);

    为了澄清一点,我将分别计算符号:

    float sign = (((mixValue / fabs(mixValue)) + (inputLevel / fabs(inputLevel))+1) / fabs(((mixValue / fabs(mixValue)) + (inputLevel / fabs(inputLevel))+1)))*-1;
    mixValue = (mixValue + inputLevel) + sign*(mixValue*inputLevel);
    

    这是浮点数学,所以您可能需要修正一些舍入问题,但这会使您走上正确的道路。

        5
  •  0
  •   doron    16 年前

    如果你担心过度的分支,看看 Duff's Device . 这应该有助于在一定程度上展开循环。事实上,循环展开是优化器要做的事情,所以用手来做可能是浪费时间。检查总成输出以查明。

    如果您对数组中的每个项执行完全相同的操作,simd肯定会提供帮助。请注意,并非所有的硬件都支持SIMD,但一些编译器(如GCC)确实为SIMD提供了内部函数,这将避免您浸入汇编程序中。

    如果使用gcc编译ARM代码,可以找到simd内部函数 here

        6
  •  0
  •   tibur    16 年前

    你对有分支和没有分支的循环做基准测试了吗?

    至少可以删除分支的一部分,因为mixValue在循环之外。

    float multiplier(float a, float b){
      unsigned char c1Neg = reinterpret_cast<unsigned char *>(&a)[3] & 0x80;
      unsigned char c2Neg = reinterpret_cast<unsigned char *>(&b)[3] & 0x80;
      unsigned char multiplierIsNeg = c1Neg & c2Neg;
      float one = 1;
      reinterpret_cast<unsigned char *>(&one)[3] |= multiplierIsNeg;
      return -one;
    }
    cout << multiplier(-1,-1) << endl; // +1
    cout << multiplier( 1,-1) << endl; // -1
    cout << multiplier( 1, 1) << endl; // -1
    cout << multiplier(-1, 1) << endl; // -1
    
        7
  •  0
  •   xtofl Adam Rosenfield    16 年前

    看看你的代码,你会发现你总是将 mixValue inputLevel ,除非两者均为阳性。

    有了一些小技巧和IEEE浮点知识,您就可以摆脱条件:

    // sets the first bit of f to zero => makes it positive.
    void absf( float& f ) {
       assert( sizeof( float ) == sizeof( int ) );
       reinterpret_cast<int&>( f ) &= ~0x80000000;
    }
    
    // returns a first-bit = 1 if f is positive
    int pos( float& f ) {
      return ~(reinterpret_cast<int&>(f) & 0x80000000) & 0x80000000;
    }
    
    // returns -fabs( f*g ) if f>0 and g>0, fabs(f*g) otherwise.    
    float prod( float& f, float& g ) {
      float p = f*g;
      float& rp=p;
      int& ri = reinterpret_cast<int&>(rp);
      absf(p);
      ri |= ( pos(f) & pos(g) & 0x80000000); // first bit = + & +
      return p;
    }
    
    int main(){
     struct T { float f, g, r; 
        void test() {
           float p = prod(f,g);
           float d = (p-r)/r;
           assert( -1e-15 < d && d < 1e-15 );
        }
     };
     T vals[] = { {1,1,-1},{1,-1,1},{-1,1,1},{-1,-1,1} };
     for( T* val=vals; val != vals+4; ++val ) {
        val->test();
     }
    }
    

    最后:你的循环

    for( ... ) {
        mixedResult += inputLevel + prod(mixedResult,inputLevel);
    }
    

    注意:你的积累规模不匹配。这个 输入电平 是无量纲的量,而 mixedResult 是你的…结果(例如,以帕斯卡为单位,以伏特为单位,…)。不能添加两个尺寸不同的数量。也许你想要 mixedResult += prod( mixedResult, inputLevel ) 作为你的蓄能器。

        8
  •  0
  •   Shelwien    16 年前

    一些编译器(即msc)也需要手动签名检查。

    来源:

    volatile float mixValue;
    volatile float inputLevel;
    
    float u   = mixValue*inputLevel;
    float v   = -u;
    float a[] = { v, u };
    
    mixValue = (mixValue + inputLevel) + a[ (inputLevel<0.0) & (mixValue<0.0) ];
    

    英特尔11.1:

    movss     xmm1, DWORD PTR [12+esp]    
    mulss     xmm1, DWORD PTR [16+esp]    
    movss     xmm6, DWORD PTR [12+esp]    
    movss     xmm2, DWORD PTR [16+esp]    
    movss     xmm3, DWORD PTR [16+esp]    
    movss     xmm5, DWORD PTR [12+esp]    
    xorps     xmm4, xmm4                  
    movaps    xmm0, xmm4                  
    subss     xmm0, xmm1                  
    movss     DWORD PTR [esp], xmm0       
    movss     DWORD PTR [4+esp], xmm1     
    addss     xmm6, xmm2                  
    xor       eax, eax                    
    cmpltss   xmm3, xmm4                  
    movd      ecx, xmm3                   
    neg       ecx                         
    cmpltss   xmm5, xmm4                  
    movd      edx, xmm5                   
    neg       edx                         
    and       ecx, edx                    
    addss     xmm6, DWORD PTR [esp+ecx*4] 
    movss     DWORD PTR [12+esp], xmm6    
    

    海湾合作委员会4.5:

    flds    32(%esp)
    flds    16(%esp)
    fmulp   %st, %st(1)
    fld     %st(0)
    fchs
    fstps   (%esp)
    fstps   4(%esp)
    flds    32(%esp)
    flds    16(%esp)
    flds    16(%esp)
    flds    32(%esp)
    fxch    %st(2)
    faddp   %st, %st(3)
    fldz
    fcomi   %st(2), %st
    fstp    %st(2)
    fxch    %st(1)
    seta    %dl
    xorl    %eax, %eax
    fcomip  %st(1), %st
    fstp    %st(0)
    seta    %al
    andl    %edx, %eax
    fadds   (%esp,%eax,4)
    xorl    %eax, %eax
    fstps   32(%esp)