Исходя из моих собственных тестов, я до сих пор прихожу к следующим выводам (но поскольку у меня нет тестовой лаборатории в моем распоряжении, мои данные наблюдений ограничены, а присяжные все еще отсутствуют):
Не имеет значения, выполняются ли операции в области одинарной или двойной точности.Фактически, большинство задействованных функций, кажется, работают немного быстрее в их воплощении двойной точности, даже когда это требует дополнительных преобразований.
Функции одинарной точности без суффикса f
(например,ilogb
) следует избегать, поскольку они, как правило, работают хуже, чем их f
суффиксные аналоги (например, ilogbf
).
"битовое смещение" не имеет себе равных с точки зрения производительности.Удивительно, но это также работает лучше в 64-битной области (опять же, я тестирую на 64-битной машине).Я вижу менее 1 нс за исполнение.Для сравнения, мой «испытательный стенд» сам весит около 15 нс за итерацию.
Что касается реализации подхода «pow2 (floor (log2))»), вот что яна данный момент:
Я не вижу никакой специальной комбинации базовых строительных блоков, которая бы дала прирост производительности от неожиданных синергетических эффектов, поэтому это кажется разумнымрассмотреть типы строительных блоков ("pow2", "floor (log2)" и sign fix) по отдельности.
Предполагая, что случай 0,0 не представляет особого интереса, самый быстрый способ обработкизнак должен по существу выполнить операцию «pow2 (floor (log2 (abs))))», а затем зафиксировать знак простым if(a<0) b=-b;
, который примерно на 5 нс быстрее copysign
.Если строительный блок "pow2" имеет фактор, подобный мантиссе (как ldexp
), использование сравнения для выбора положительного или отрицательного фактора также является приемлемым вариантом, поскольку он лишь немного медленнее, чем условное исправление после операции.
Безусловно, худший выбор для операции «pow2» (и тот, который программное обеспечение, над которым я работаю, использовался целую вечность в двух реализациях) - наивно использовать pow(2.0,x)
,В то время как компилятор мог предположительно оптимизировать его во что-то намного более быстрое, мой нет.exp2
примерно на 60 нс быстрее.ldexp
еще на 15 нс быстрее, что делает его лучшим выбором, с весом приблизительно 10-10 нс.
Существует еще более быстрая опция (также используется в программном обеспеченииЯ работаю над этим), а именно с использованием битовых сдвигов в целочисленной области, но это достигается ценой строгого ограничения диапазона значений, для которых работает функция.Если этот путь будет решен, операция должна быть выполнена в домене long long
, поскольку она лишь немного медленнее, чем в домене int
.Этот подход может сэкономить еще 4-5 нс.
Самый медленный строительный блок "floor (log2)", который я мог найти (кроме (int)(log(x)/log(2))
, который я даже не удосужилсятест) был (int)log2(fabs(x))
и их родня.frexp
примерно на 30 нс быстрее, взвешиваясь с предположительными 8-10 нс.
Если тип с плавающей запятой использует представление base-2, ilogb
является жизнеспособнымальтернатива frexp
и сохраняет еще 1 нс.logb
немного медленнее, чем ilogb
(наравне с frexp
), что, я думаю, имеет смысл.
В общем, пока что следующие реализации кажутсяСтоит учесть:
double Pow2Trunc(double a)
{
union { double f; uint64_t i; } hack;
hack.f = a;
hack.i &= 0xFFF0000000000000u;
return hack.f;
}
- самая быстрая реализация (около 1 нс), при условии, что специальные значения не имеют значения, известен двоичный формат с плавающей запятой (в данном случае двоичный код IEEE64) и тип intдоступен тот же размер и порядок байтов;
double Pow2Trunc(double a)
{
int exp;
(void)frexp(a,&exp);
double b = ldexp(0.5, exp);
if (a < 0) b = -b;
return b;
}
- самая быстрая полностью переносимая реализация (около 16 нс);и, возможно,
double Pow2Trunc(double a)
{
double b = ldexp(1.0, ilogb(a));
if (a < 0) b = -b;
return b;
}
- немного менее переносимая, но и несколько более быстрая альтернатива (около 15 нс).
(обработка специальных значений может быть улучшена; для моего случая использования, однако онине имеет значения, достаточного для дальнейшего рассмотрения.)
Предоставление альтернатив на основе float
, похоже, не стоит усилий;если они предоставлены, важно использовать f
-софиксированные варианты функций.
Очевидно, что эти результаты зависят от аппаратной платформы, компилятора и настроек (i7-5820K, Windows 10 Subsystemдля Linux g ++ 5.4.0, -std=gnu++11 -o3 -ffast-math
).Пробег других сред может варьироваться, и изучение случаев, когда результаты качественно отличаются, будет для меня наиболее ценным .