Хороший способ сделать быстрый разрыв на С++?

Иногда я вижу и использовал следующий вариант для быстрого деления в С++ с номерами с плавающей запятой.

// orig loop
double y = 44100.0;
for(int i=0; i<10000; ++i) {
double z = x / y;
}

// alternative
double y = 44100;
double y_div = 1.0 / y;

for(int i=0; i<10000; ++i) {
double z = x * y_div;
}

Но кто-то недавно намекнул, что это может быть не самый точный способ.

Любые мысли?

Ответ 1

Почти на каждом процессоре деление с плавающей запятой в несколько раз дороже, чем умножение числа с плавающей запятой, поэтому умножение на инверсию вашего делителя является хорошей оптимизацией. Недостатком является то, что существует вероятность того, что на некоторых процессорах вы потеряете очень небольшую часть точности, например, на современных процессорах x86, 64-битные операции с плавающей точкой фактически вычисляются с использованием 80 бит при использовании режима FPU по умолчанию и хранения он отключается в переменной, что приведет к усечению этих лишних битов точности в соответствии с вашим режимом округления FPU (который по умолчанию близок к ближайшему). Это действительно имеет значение, если вы объединяете многие операции с плавающей точкой и должны беспокоиться об накоплении ошибок.

Ответ 2

Wikipedia соглашается, что это может быть быстрее. Связанная статья также содержит несколько других алгоритмов быстрого деления, которые могут представлять интерес.

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

Ответ 3

Ваш оригинальный

// original loop:
double y = 44100.0;

for(int i=0; i<10000; ++i) {
    double z = x / y;
}

можно легко оптимизировать до

// haha:
double y = 44100.0;
double z = x / y;

и производительность довольно приятная.; -)

РЕДАКТИРОВАТЬ: Люди продолжают голосовать, поэтому здесь не такая смешная версия:

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

Таким образом, единственный способ повысить производительность - это знать, какую конкретную ситуацию у вас есть, и сделать для этого оптимальный код. Скорее всего, это полная трата времени, потому что ваша программа медленна по какой-то другой причине, например, при выполнении математических инвариантов цикла. Вместо этого найдите лучший алгоритм.

Ответ 4

Аудио, hunh? Это не только 44,100 делений в секунду, когда у вас есть, скажем, пять треков звука, работающих одновременно. В конце концов, даже простой фейдер потребляет циклы. И это просто для довольно голых костей, минимальный пример - что, если вы хотите иметь, скажем, экв и компрессор? Может быть, немного реверберации? Ваш общий математический бюджет, так сказать, быстро съедается. В этих случаях имеет смысл отжать немного дополнительной производительности.

Профилиры хороши. Профилиров - ваш друг. Профиляторы заслуживают минеты и пудинг. Но вы уже знаете, где основная шея бутылки находится в звуковой работе - она ​​в цикле обрабатывает образцы, и чем быстрее вы сможете это сделать, тем счастливее будут ваши пользователи. Используйте все, что сможете! Умножьте по взаимности, сдвигайте биты, когда это возможно (exp (x * y) = exp (x) * exp (y), в конце концов), используйте таблицы поиска, ссылайтесь на переменные по ссылке вместо значений (меньше нажатия/выскакивания в стеке), термины рефакторинга и т.д. (Если вы хороши, вы будете смеяться над этими элементарными оптимизациями.)

Ответ 5

В вашем примере, используя gcc, деление с параметрами -O3 -ffast-math дает тот же код, что и умножение без -ffast-math. (В тестовой среде с достаточным количеством летучих элементов вокруг этого цикла все еще есть.)

Итак, если вы действительно хотите оптимизировать эти подразделения и не заботитесь о последствиях, вот путь. Умножение кажется примерно в 15 раз быстрее, кстати.

Ответ 6

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

Ответ 7

При обработке звука я предпочитаю вместо этого использовать математику с фиксированной точкой. Я полагаю, это зависит от уровня точности, который вам нужен. Но предположим, что 16.16 целых чисел с фиксированной точкой (что означает, что 16 бит - это целое число, а 16 - это доля). Теперь все вычисления могут быть выполнены как простые целочисленные математические выражения:

unsigned int y = 44100 << 16;
unsigned int z = x / (y >> 16);  // divisor must be the whole number portion

Или с помощью макросов, которые помогут:

#define FP_INT(x) (x << 16)
#define FP_MUL(x, y) (x * (y >> 16))
#define FP_DIV(x, y) (x / (y >> 16))

unsigned int y = FP_INT(44100);
unsigned int z = FP_MUL(x, y);

Ответ 8

Я повторяю 10 000 раз, чтобы сделать код достаточно длинным, чтобы легко измерить время. Или у вас есть 10000 номеров для разделения на один и тот же номер? Если первый, поставьте "y_div = 1.0/y;" внутри цикла, потому что это часть операции.

Если последнее, да, умножение с плавающей запятой, как правило, быстрее, чем деление. Однако не изменяйте свой код от очевидного до тайного на основе догадок. Сначала проверьте, чтобы найти медленные точки, а затем оптимизировать их (и провести измерения до и после, чтобы убедиться, что ваша идея на самом деле вызывает улучшение).

Ответ 9

Я исхожу из исходного сообщения, что x не является показанной там константой, но, вероятно, данные из массива, поэтому x [i], вероятно, будет источником данных и аналогичным образом для вывода, он будет храниться где-то в памяти.

Я полагаю, что если число циклов действительно составляет 10 000, как в исходном сообщении, это будет иметь мало значения, которое вы используете, поскольку весь цикл даже не займет миллисекунды в любом случае на современном процессоре. Если число циклов действительно намного выше, возможно, 1 000 000 или более, тогда я ожидаю, что стоимость доступа к памяти, скорее всего, сделает более быструю операцию полностью нерелевантной, так как она всегда будет ожидать данных в любом случае.

Я предлагаю попробовать как с вашим кодом, так и с тестированием, если он действительно вносит существенные изменения во время выполнения, а если нет, то просто напишите прямое деление, если это нужно алгоритму.

Ответ 10

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

Ответ 11

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

В современных процессорах математика с плавающей запятой ослепляется быстро, и оптимизация компиляторов обычно может творить чудеса с тонкой настройкой.

Практически не стоит прилагать никаких небольших оптимизаций.

Попробуйте сделать свой код простым и идиотским. Только вы найдете реальное узкое место (используя профилировщик), подумаете ли вы об оптимизации в расчетах с плавающей точкой.