оптимизация, устранение ветвления - PullRequest
4 голосов
/ 02 августа 2010
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 ]

4 голосов
/ 02 августа 2010

Как насчет этого:

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

Редактировать: Майк был прав, что && представит ветку, и спасибо Педро за доказательство этого. Я изменил && на & и теперь GCC (версия 4.4.0) генерирует код без ответвлений.

1 голос
/ 02 августа 2010

Вдохновленный ответом Року (который на 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, Режим разблокировки):

Разборка http://img248.imageshack.us/img248/6865/floattestbranchmine.png

1 голос
/ 02 августа 2010
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);
}
0 голосов
/ 03 августа 2010

Некоторые компиляторы (например, 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) ];

IntelC 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    

gcc 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)
0 голосов
/ 03 августа 2010

Взглянув на свой код, вы увидите, что вы всегда добавляете абсолютные значения mixValue и inputLevel, за исключением случаев, когда оба значения положительны.может избавиться от условного:

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

Примечание: размеры вашего накопления не совпадают.inputLevel - это безразмерная величина, а mixedResult - это ваш ... результат (например, в Паскале, в Вольтах, ...).Вы не можете добавить две величины с разными размерами.Возможно, вы хотите mixedResult += prod( mixedResult, inputLevel ) в качестве аккумулятора.

0 голосов
/ 02 августа 2010

С макушки головы (уверен, ее можно уменьшить):

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

Это математика с плавающей запятой, поэтому вам, скорее всего, придется исправить некоторые проблемы с округлением, но я думаю, это должно привести вас на правильный путь.

0 голосов
/ 02 августа 2010

Если вас беспокоит чрезмерное разветвление, посмотрите на Устройство Даффа .Это должно помочь немного раскрутить петлю.По правде говоря, раскручивание цикла - это то, что будет сделано оптимизатором, поэтому попытка сделать это вручную может быть пустой тратой времени.Проверьте выходные данные сборки, чтобы узнать.

SIMD определенно поможет, если вы выполните точно такую ​​же операцию для каждого элемента в вашем массиве.Имейте в виду, что не все аппаратное обеспечение поддерживает SIMD, но некоторые компиляторы, такие как gcc, предоставляют встроенные функции для SIMD, что избавит вас от погружения в ассемблер.

Если вы используете gcc для компиляции кода ARM, можно найти встроенные SIMD здесь

0 голосов
/ 02 августа 2010

Вы тестировали петлю с ответвлением и без него?

По крайней мере, вы можете удалить одну часть ветви, так как 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
...