Я профилирую свой код и оптимизировал все, что мог, переходя к функции, которая выглядит примерно так:
double func(double a, double b, double c, double d, int i){
if(i > 10 && a > b || i < 11 && a < b)
return abs(a-b)/c;
else
return d/c;
}
Он вызывается миллионы раз во время выполнения программы, и профилировщик показывает мне, что ~ 80% всего времени уходит на вызовы abs()
,
Я заменил abs()
с fabs()
и это дало примерно 10% ускорения, что не имеет особого смысла для меня, поскольку я много раз слышал, что они одинаковы для чисел с плавающей запятой и abs()
следует использовать всегда. Это неправда или я что-то упустил?
Каков был бы самый быстрый способ оценить абсолютное значение для двойного, что могло бы еще больше улучшить производительность?
Если это имеет значение, я использую g++
на Linux X86_64.
Сделайте все 3 вычисления. Вставьте результат в массив из 3 элементов. Используйте не ветвящуюся арифметику, чтобы найти правильный индекс массива. Верните этот результат.
То есть,
bool icheck = i > 10;
bool zero = icheck & (a > b);
bool one = !icheck & (b > a);
bool two = !zero & !one;
int idx = one | (two << 1);
return val[idx];
куда val
содержит результат трех вычислений. Использование &
вместо &&
это важно.
Это устраняет проблемы предсказания вашей ветви. Наконец, убедитесь, что циклический код может видеть реализацию, так что издержки вызова могут быть устранены.
Интересный вопрос.
double func(double a, double b, double c, double d, int i){
if(i > 10 && a > b || i < 11 && a < b)
return abs(a-b)/c;
else
return d/c;
}
Первые мысли таковы:
Я собираюсь предположить, что a никогда не равно b — мой инстинкт инстинкта состоит в том, что есть 50% -ная вероятность, что это верно для вашего набора данных, и это допускает некоторые интересные оптимизации. Если это не правда, то мне нечего предположить, что Якк этого не сделал.
double amb = a - b;
bool altb = a < b; // or signbit(amb) if it proves faster for you
double abs_amb = (1 - (altb << 1)) * amb;
bool use_amb = i > 10 != altb;
return (use_amb * abs_amb + !use_amb * d) / c;
Одна из целей, о которых я помнил при структурировании работы, состояла в том, чтобы обеспечить некоторый параллелизм в конвейере выполнения процессора; это можно проиллюстрировать так:
amb altb i > 10
\ / \ /
abs_amb use_amb
\ / \
use_amb*abs_amb !use_amb*d
\ /
+ /c
Вы пытались развернуть, если так:
double func(double a, double b, double c, double d, int i){
if(i > 10 && a > b)
return (a-b)/c;
if (i < 11 && a < b)
return (b-a)/c;
return d/c;
}
Я бы посмотрел на сборку, созданную с помощью вызова fabs (). Это может быть накладные расходы при вызове функции. Если это так, замените его встроенным раствором. Если действительно стоит проверить абсолютное значение, это дорого, попробуйте поразрядно и (&) с битовой маской, которая равна 1 везде, кроме знакового бита. Я сомневаюсь, что это будет дешевле, чем то, что генерирует fabs () производителя стандартной библиотеки.