Ускорение двойного абсолютного значения в C++

Я профилирую свой код и оптимизировал все, что мог, переходя к функции, которая выглядит примерно так:

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(),

  1. Я заменил abs() с fabs() и это дало примерно 10% ускорения, что не имеет особого смысла для меня, так как я много раз слышал, что они одинаковы для чисел с плавающей запятой и abs() следует использовать всегда. Это неправда или я что-то упустил?

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

Если это имеет значение, я использую g++ на Linux X86_64.

4 ответа

Решение

Сделайте все 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 () производителя стандартной библиотеки.

Другие вопросы по тегам