2013-05-23 7 views
3

Я профилирование моего кода и оптимизированное вся, что мог, спустившись к функции, которая выглядит примерно так:Форсирование двойного абсолютного значения в 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.

+0

Если IEEE, и вы хотите быть злым и эксплуатировать неопределенное поведение, вы можете попробовать «double x = a - b; * (uint64_t *) & x & = ~ (1ULL << 63); return x/c; ' –

+0

Просто любопытно - это когда-либо равное b? –

+0

@TonyD Предположим, нет, это не имеет значения для меня. – sashkello

ответ

6

Выполняйте все 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 держит результат трех вычислений. Важное значение имеет использование & вместо &&.

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

+1

Мне любопытно: проблемы с прогнозированием ветвлений возникают довольно часто, и этот «неветвящийся» трюк выбора является классическим, но, похоже, компиляторы не оптимизируют его даже в этом (по общему признанию) простом случае. –

+0

Справедливости ради. Неоптимизированная версия может быть быстрее, если ветви предсказуемы. Я бы не доверял коду av = bove, чтобы быть быстрее, если я не проверял его: я делаю в 2-3 раза больше работы! – Yakk

+0

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

1

Вы пробовали разворачивая если так:

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; 
} 
+0

Пока нет, позвольте мне попробовать ... – sashkello

+0

Я пробовал это. Кажется, он вышел медленнее, чем 'std :: abs' или' fabs', в VS2010 =/ – paddy

+0

Так как в моем случае a> b чаще, чем a sashkello

0

Я хотел бы посмотреть на собрании производится путем вызова FABS(). Это может быть накладные расходы на вызов функции. Если это так, замените его на встроенное решение. Если это действительно проверка правильности абсолютного значения, попробуйте побитовое и (&) с битовой маской, которая равна 1 везде, кроме знакового бита. Я сомневаюсь, что это было бы дешевле, чем то, что создает fabs().

4

Интересный вопрос.

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; 
} 

Первые мысли, что:

  • , где это "инлайн" Классификатор?
  • есть много возможностей для неверного предсказания отрасли, и
  • много коротких замыканий булевых оценок.

Я собираюсь предположить, что он никогда не равен 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; 

Одна из целей я учитывала при структурировании работы была разрешить некоторый параллелизм в трубопроводе выполнения CPU; это можно проиллюстрировать следующим образом:

amb altb i > 10 
    \/ \ /
    abs_amb use_amb 
     \/ \ 
use_amb*abs_amb !use_amb*d 
      \ /
       + /c 
+0

+1 Спасибо, это действительно помогло! Однако я согласен с ответом Якка, поскольку идея схожа, и он был первым. – sashkello

+0

Было бы интересно узнать, сколько из этого улучшилось. Благодарю. –

+0

@ c-urchin, я не тестировал точную ситуацию, мой код немного сложнее, но после использования этой идеи улучшение составляет ~ 20% для меня. – sashkello