Могу ли я создать объединение с формальным параметром, переданным функции в C ++? - PullRequest
2 голосов
/ 26 октября 2011

Функция ниже вычисляет абсолютное значение 32-битного значения с плавающей запятой:

__forceinline static float Abs(float x)
{
    union {
        float x;
        int a;
    } u;
    //u.x = x;
    u.a &= 0x7FFFFFFF;
    return u.x;
}

union u, объявленный в функции, содержит переменную x, которая отличается от x, который передается в качестве параметра в функцию. Есть ли способ создать объединение с аргументом для функции - х?

По какой причине указанная выше функция с незакомментированной строкой будет выполняться дольше, чем эта?

__forceinline float fastAbs(float a)
{
    int b= *((int *)&a) & 0x7FFFFFFF;
    return *((float *)(&b));
}

Я пытаюсь найти наилучший способ принять значение Abs с плавающей запятой как можно меньшим числом операций чтения / записи в память.

Ответы [ 2 ]

5 голосов
/ 26 октября 2011

По первому вопросу, я не уверен, почему вы не можете просто получить то, что хотите, с заданием. Компилятор будет делать любые оптимизации, которые могут быть сделаны.

В вашем втором примере кода. Вы нарушаете строгий псевдоним. Так что это не то же самое.

Почему медленнее:

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

Во втором фрагменте: a, который изначально находится в блоке с плавающей запятой (либо в FPU x87, либо в регистре SSE), необходимо переместить в регистры общего назначения для применения маски 0x7FFFFFFF. Затем его нужно вернуть назад.

В первом фрагменте: Компилятор, вероятно, достаточно умен, чтобы загружать a непосредственно в целочисленную единицу. Таким образом, вы обходите FPU на первом этапе.

(Я не уверен на 100%, пока вы не покажете нам сборку. Это также будет сильно зависеть от того, начинается ли параметр в регистре или в стеке. И будет ли вывод немедленно использоваться другой операцией с плавающей запятой .)

2 голосов
/ 26 октября 2011

Глядя на разборку кода, скомпилированного в режиме релиза, разница довольно очевидна! Я удалил inline и использовал две виртуальные функции, чтобы компилятор не слишком оптимизировал и показал различия.

Это первая функция.

013D1002  in          al,dx  
            union {
                float x;
                int a;
            } u;
            u.x = x;
013D1003  fld         dword ptr [x]   // Loads a float on top of the FPU STACK.
013D1006  fstp        dword ptr [x]   // Pops a Float Number from the top of the FPU Stack into the destination address.
            u.a &= 0x7FFFFFFF;
013D1009  and         dword ptr [x],7FFFFFFFh  // Execute a 32 bit binary and operation with the specified address.
            return u.x;
013D1010  fld         dword ptr [x]  // Loads the result on top of the FPU stack.
        }

Это вторая функция.

013D1020  push        ebp                       // Standard function entry... i'm using a virtual function here to show the difference.
013D1021  mov         ebp,esp
            int b= *((int *)&a) & 0x7FFFFFFF;
013D1023  mov         eax,dword ptr [a]         // Load into eax our parameter.
013D1026  and         eax,7FFFFFFFh             // Execute 32 bit binary and between our register and our constant.
013D102B  mov         dword ptr [a],eax         // Move the register value into our destination variable
            return *((float *)(&b));
013D102E  fld         dword ptr [a]             // Loads the result on top of the FPU stack.

Количество операций с плавающей запятой и использование стека FPU в первом случае больше. Функции выполняют именно то, что вы просили, поэтому неудивительно. Поэтому я ожидаю, что вторая функция будет быстрее.

Теперь ... удаление виртуальных и встроенных вещей немного отличается, сложно написать здесь код дизассемблирования, потому что, конечно, компилятор делает хорошую работу, но я повторяю, если значения не являются константами, компилятор будет использовать больше операций с плавающей точкой в ​​первой функции. Конечно, целочисленные операции выполняются быстрее, чем операции с плавающей запятой.

Вы уверены, что прямое использование функции math.h abs медленнее, чем ваш метод? Если правильно указано, функция abs просто сделает это!

00D71016  fabs  

Подобные микрооптимизации трудно увидеть в длинном коде, но если ваша функция вызывается в длинной цепочке операций с плавающей запятой, fabs будет работать лучше, поскольку значения уже будут в стеке FPU или в регистрах SSE! abs будет быстрее и лучше оптимизирован компилятором.

Вы не можете измерить производительность оптимизаций, выполняющих цикл в куске кода, вы должны увидеть, как компилятор смешивает все вместе в реальном коде.

...