производительность — есть ли быстрая замена fabsf на «float»? в С ++?

Я просто делаю некоторый бенчмаркинг и узнал, что fabsf() часто в 10 раз медленнее, чем fabs(), Так я его разобрал и получается double версия использует fabs инструкция, float версии нет. Можно ли это улучшить? Это быстрее, но не так много, и я боюсь, что это может не сработать, это слишком низкий уровень:

float mabs(float i)
{
(*reinterpret_cast<MUINT32*>(&i)) &= 0x7fffffff;
return i;
}

Изменить: Извините забыл о компиляторе — я все еще использую старый добрый VS2005, никаких специальных библиотек.

1

Решение

Вы можете легко проверить различные возможности, используя код ниже. По сути, это проверяет вашу битву против наивного шаблона абс, и std::abs, Не удивительно, что наивный шаблон abs побеждает. Ну, как ни удивительно, это побеждает. Я бы ожидал std::abs быть одинаково быстрым Обратите внимание, что -O3 на самом деле делает вещи медленнее (по крайней мере, на колиру).

Хост-система Coliru показывает следующее:

random number generation: 4240 ms
naive template abs: 190 ms
ugly bitfiddling abs: 241 ms
std::abs: 204 ms
::fabsf: 202 ms

И эти временные характеристики для виртуальной машины Virtualbox с Arch с GCC 4.9 на Core i7:

random number generation: 1453 ms
naive template abs: 73 ms
ugly bitfiddling abs: 97 ms
std::abs: 57 ms
::fabsf: 80 ms

И эти сроки на MSVS2013 (Windows 7 x64):

random number generation: 671 ms
naive template abs: 59 ms
ugly bitfiddling abs: 129 ms
std::abs: 109 ms
::fabsf: 109 ms

Если я не совершил явно очевидную ошибку в этом тестовом коде (не стреляйте в меня из-за этого, я написал это примерно через 2 минуты), я бы сказал, просто используйте std::absили версия шаблона, если она окажется немного быстрее для вас.


Код:

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <random>
#include <vector>

#include <math.h>

using Clock = std::chrono::high_resolution_clock;
using milliseconds = std::chrono::milliseconds;

template<typename T>
T abs_template(T t)
{
return t>0 ? t : -t;
}

float abs_ugly(float f)
{
(*reinterpret_cast<std::uint32_t*>(&f)) &= 0x7fffffff;
return f;
}

int main()
{
std::random_device rd;
std::mt19937 mersenne(rd());
std::uniform_real_distribution<> dist(-std::numeric_limits<float>::lowest(), std::numeric_limits<float>::max());

std::vector<float> v(100000000);

Clock::time_point t0 = Clock::now();

std::generate(std::begin(v), std::end(v), [&dist, &mersenne]() { return dist(mersenne); });

Clock::time_point trand = Clock::now();

volatile float temp;
for (float f : v)
temp = abs_template(f);

Clock::time_point ttemplate = Clock::now();

for (float f : v)
temp = abs_ugly(f);

Clock::time_point tugly = Clock::now();

for (float f : v)
temp = std::abs(f);

Clock::time_point tstd = Clock::now();

for (float f : v)
temp = ::fabsf(f);

Clock::time_point tfabsf = Clock::now();

milliseconds random_time = std::chrono::duration_cast<milliseconds>(trand - t0);
milliseconds template_time = std::chrono::duration_cast<milliseconds>(ttemplate - trand);
milliseconds ugly_time = std::chrono::duration_cast<milliseconds>(tugly - ttemplate);
milliseconds std_time = std::chrono::duration_cast<milliseconds>(tstd - tugly);
milliseconds c_time = std::chrono::duration_cast<milliseconds>(tfabsf - tstd);
std::cout << "random number generation: " << random_time.count() << " ms\n"<< "naive template abs: " << template_time.count() << " ms\n"<< "ugly bitfiddling abs: " << ugly_time.count() << " ms\n"<< "std::abs: " << std_time.count() << " ms\n"<< "::fabsf: " << c_time.count() << " ms\n";
}

Да, и чтобы ответить на ваш реальный вопрос: если компилятор не может генерировать более эффективный код, я сомневаюсь, что есть более быстрый способ сохранения для микрооптимизированной сборки, особенно для таких элементарных операций, как этот.

3

Другие решения

Здесь есть много вещей. Прежде всего, сопроцессор x87 устарел в пользу SSE / AVX, поэтому я удивлен, прочитав, что ваш компилятор все еще использует fabs инструкция. Вполне возможно, что другие, которые разместили контрольные ответы на этот вопрос, используют платформу, которая поддерживает SSE. Ваш результаты могут быть совершенно разными.

Я не уверен, почему ваш компилятор использует другую логику для fabs а также fabsf, Это вполне возможно, чтобы загрузить float к стеку x87 и использовать fabs Инструкция по этому так же легко. Проблема с воспроизведением этого самостоятельно, без поддержки компилятора, состоит в том, что вы не можете интегрировать операцию в обычный оптимизирующий конвейер компилятора: если вы говорите «загрузить этот float, используйте fabs инструкция, верните этот float в память «, тогда компилятор сделает именно это … и это может включать в себя возвращение в память float, который уже был готов к обработке, загрузку его обратно, используя fabs инструкции, помещая его обратно в память и загружая его снова в стек x87, чтобы возобновить нормальный оптимизируемый конвейер. Это было бы четырьмя потерянными операциями хранения нагрузки, потому что это нужно было только сделать fabs,

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

Для справки, современные компиляторы и современные платформы используют инструкции SSE andps (для поплавков) и andpd (для удвоений) до И из знака бита, очень похоже на то, что вы делаете сами, но избегая всех проблем семантики языка. Они оба так быстро. Современные компиляторы также могут обнаруживать такие паттерны, как x < 0 ? -x : x и производить оптимальные andps/andpd Инструкция без необходимости встроенного компилятора.

2

Вы пробовали std::abs перегрузка для float? Это было бы каноническим способом C ++.

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

2
По вопросам рекламы [email protected]