У Джона Кармака есть специальная функция в исходном коде Quake III, которая вычисляет обратный квадратный корень из числа с плавающей запятой, в 4 раза быстрее обычного (float)(1.0/sqrt(x))
, включая странную 0x5f3759df
константу. См. Код ниже. Может ли кто-нибудь объяснить построчно, что именно здесь происходит и почему это работает намного быстрее, чем обычная реализация?
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y;
i = 0x5f3759df - ( i >> 1 );
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) );
#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) );
#endif
#endif
return y;
}
Ответы:
FYI. Кармак этого не писал. Терье Матисен и Гэри Таролли частично (и очень скромно) признают это, а также ссылаются на некоторые другие источники.
Как возникла мифическая константа, остается загадкой.
Процитирую Гэри Таролли:
Чуть лучшая константа, разработанная математиком-экспертом (Крис Ломонт), пытающимся выяснить, как работал исходный алгоритм:
Несмотря на это, его первоначальная попытка математически «превосходной» версии id sqrt (которая пришла к почти той же константе) оказалась хуже той, которая была первоначально разработана Гэри, несмотря на то, что математически была намного «более чистой». Он не мог объяснить, почему id был таким отличным iirc.
источник
Конечно, в наши дни это оказывается намного медленнее, чем просто использование sqrt FPU (особенно на 360 / PS3), потому что переключение между регистрами с плавающей запятой и int вызывает загрузку-хит-хранилище, в то время как модуль с плавающей запятой может делать обратный квадрат корень в железе.
Это просто показывает, как должна развиваться оптимизация по мере изменения характера лежащего в основе оборудования.
источник
Грег Хьюгилл и IllidanS4 дали ссылку с отличным математическим объяснением. Я постараюсь подвести итог для тех, кто не хочет вдаваться в подробности.
Любая математическая функция, за некоторыми исключениями, может быть представлена полиномиальной суммой:
можно в точности преобразовать в:
Где a0, a1, a2, ... - константы . Проблема в том, что для многих функций, таких как квадратный корень, для точного значения эта сумма имеет бесконечное количество членов, она не заканчивается на некотором x ^ n . Но, если мы остановимся на некотором x ^ n мы все равно получим результат с некоторой точностью.
Итак, если у нас есть:
В этом конкретном случае они решили отбросить все полиномиальные члены выше второго, вероятно, из-за скорости вычислений:
И теперь задача сводилась к тому, чтобы вычислить a0 и a1, чтобы y имел наименьшее отличие от точного значения. Они подсчитали, что наиболее подходящими значениями являются:
Итак, когда вы поместите это в уравнение, вы получите:
Это то же самое, что и строка, которую вы видите в коде:
Изменить: на самом деле здесь
y = 0x5f375a86 - 0.5*x
не то же самое, что иi = 0x5f375a86 - (i >> 1);
смещение числа с плавающей запятой как целого числа не только делится на два, но также делит экспоненту на два и вызывает некоторые другие артефакты, но все же сводится к вычислению некоторых коэффициентов a0, a1, a2 ....На данный момент они обнаружили, что точности этого результата недостаточно для этой цели. Таким образом, они дополнительно проделали только один шаг итерации Ньютона для повышения точности результата:
Они могли бы сделать еще несколько итераций в цикле, каждая из которых улучшала бы результат, пока не будет достигнута требуемая точность. Именно так это работает в CPU / FPU!Но, похоже, хватило всего одной итерации, что тоже было благом для скорости. CPU / FPU выполняет столько итераций, сколько необходимо для достижения точности для числа с плавающей запятой, в котором сохраняется результат, и имеет более общий алгоритм, который работает для всех случаев.
Короче говоря, они сделали следующее:
Используйте (почти) тот же алгоритм, что и CPU / FPU, используйте улучшение начальных условий для особого случая 1 / sqrt (x) и не рассчитывайте полностью до точности, к которой CPU / FPU перейдет, но остановится раньше, таким образом набирает скорость расчета.
источник
Согласно этой хорошей статье, написанной некоторое время назад ...
Это действительно хорошее чтение. Это только крошечный кусочек.
источник
Мне было любопытно узнать, что это за константа в виде числа с плавающей запятой, поэтому я просто написал этот фрагмент кода и погуглил целое число, которое выскочило.
Похоже, что константа - это «Целочисленное приближение к квадратному корню из 2 ^ 127, более известное по шестнадцатеричной форме его представления с плавающей запятой, 0x5f3759df» https://mrob.com/pub/math/numbers-18.html
На том же сайте все объясняется. https://mrob.com/pub/math/numbers-16.html#le009_16
источник