Подтвердить что ты не робот

Рассчитывает ли Sqrt (x) как x * InvSqrt (x) какой-либо смысл в коде Doom 3 BFG?

Я просмотрел недавно выпущенный исходный код Doom 3 BFG, когда я столкнулся с чем-то, что, похоже, не имеет никакого смысла. Doom 3 завершает математические функции в классе idMath. Некоторые из функций просто соответствуют соответствующим функциям из math.h, но некоторые из них являются переопределениями (например, idMath:: exp16()), которые, как я предполагаю, имеют более высокую производительность, чем их math.h аналогов (возможно, за счет точности).

Что меня озадачивает, так это то, как они реализовали функцию float idMath::Sqrt(float x):

ID_INLINE float idMath::InvSqrt( float x ) {
     return ( x > FLT_SMALLEST_NON_DENORMAL ) ? sqrtf( 1.0f / x ) : INFINITY;
}

ID_INLINE float idMath::Sqrt( float x ) {
     return ( x >= 0.0f ) ? x * InvSqrt( x ) : 0.0f;
}

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

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

ID_INLINE float idMath::InvSqrt( float x ) {

    dword a = ((union _flint*)(&x))->i;
    union _flint seed;

    assert( initialized );

    double y = x * 0.5f;
    seed.i = (( ( (3*EXP_BIAS-1) - ( (a >> EXP_POS) & 0xFF) ) >> 1)<<EXP_POS) | iSqrt[(a >> (EXP_POS-LOOKUP_BITS)) & LOOKUP_MASK];
    double r = seed.f;
    r = r * ( 1.5f - r * r * y );
    r = r * ( 1.5f - r * r * y );
    return (float) r;
}


ID_INLINE float idMath::Sqrt( float x ) {
    return x * InvSqrt( x );
}

Вы видите какое-либо преимущество при вычислении Sqrt(x) как x * InvSqrt(x), если InvSqrt(x) внутренне просто вызывает math.h fsqrt(1.f/x)? Возможно, мне не хватает чего-то важного для денормализованных чисел с плавающей запятой или это просто небрежность в части программного обеспечения id?

4b9b3361

Ответ 1

Я вижу две причины для этого: во-первых, метод "быстрый invSqrt" (действительно, Newton Raphson) теперь является методом, используемым во многих аппаратных средствах, поэтому этот подход открывает возможность воспользоваться такими (и, возможно, четыре или более таких операций одновременно). В этой статье это немного обсуждается:

Как медленно (сколько циклов) вычисляется квадратный корень?

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

Ответ 2

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

В их случае им не нужна огромная точность при вычислении этих чисел, поэтому инженеры, стоящие за кодом Doom 3 (первоначально Quake III), разработали очень быстрый метод вычисления приближения для InvSqrt, используя только несколько итераций Ньютона-Рафсона.

Вот почему они используют InvSqrt во всем своем коде вместо использования встроенных (более медленных) функций. Я полагаю, что использование x * InvSqrt(x) заключается в том, чтобы избежать умножения работы на два (с помощью двух очень эффективных функций, один для InvSqrt и другого для Sqrt).

Вы должны прочитать эту статью, это может пролить свет на эту проблему.

Ответ 3

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

Однако, учитывая треть века опыта программирования, этот код соответствует шаблону, о котором говорили другие: в свое время InvSqrt был быстрым, и имеет смысл использовать его для вычисления квадратного корня. Затем InvSqrt изменился, и никто не обновил Sqrt.

Ответ 4

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