Это просто для удовлетворения моего собственного любопытства.
Есть ли реализация этого:
float InvSqrt (float x)
{
float xhalf = 0.5f*x;
int i = *(int*)&x;
i = 0x5f3759df - (i>>1);
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x);
return x;
}
в ржавчине? Если он существует, отправьте код.
Я попробовал это и потерпел неудачу. Я не знаю, как кодировать число с плавающей точкой, используя целочисленный формат. Вот моя попытка:
fn main() {
println!("Hello, world!");
println!("sqrt1: {}, ",sqrt2(100f64));
}
fn sqrt1(x: f64) -> f64 {
x.sqrt()
}
fn sqrt2(x: f64) -> f64 {
let mut x = x;
let xhalf = 0.5*x;
let mut i = x as i64;
println!("sqrt1: {}, ", i);
i = 0x5f375a86 as i64 - (i>>1);
x = i as f64;
x = x*(1.5f64 - xhalf*x*x);
1.0/x
}
Ссылка:
1. Происхождение Быстрого InvSqrt () Quake3 - Страница 1
2. Понимание быстрого обратного квадратного корня Quake
3. БЫСТРАЯ ОБРАТНАЯ КВАДРАТНАЯ КОРНА.pdf
4. Исходный код: q_math.c # L552-L572
union
.union
работает.memcpy
определенно работает, хотя это многословно.rsqrtss
иrsqrtps
инструкция, введенная с Pentium III в 1999 году, быстрее и точнее , чем этот код. ARM NEON имеетvrsqrte
что похоже. И какие бы вычисления Quake III ни использовал для этого, в любом случае, вероятно, в наши дни будет сделано на GPU.Ответы:
Для этого есть функция,
f32::to_bits
которая возвращаетu32
. Существует также функция для другого направления: онаf32::from_bits
принимаетu32
аргумент as. Эти функции предпочтительнее, чемmem::transmute
последние,unsafe
и их сложно использовать.С этим, вот реализация
InvSqrt
:( Детская площадка )
Эта функция компилируется в следующую сборку на x86-64:
Я не нашел ни одной справочной сборки (если есть, скажите, пожалуйста!), Но она мне кажется довольно хорошей. Я просто не уверен, почему поплавок был перемещен
eax
только для того, чтобы сделать сдвиг и целочисленное вычитание. Может быть, регистры SSE не поддерживают эти операции?clang 9.0 с
-O3
компилирует код C в основном в ту же сборку . Так что это хороший знак.Стоит отметить, что если вы действительно хотите использовать это на практике: пожалуйста, не делайте этого. Как отметил Бенгр в комментариях , современные процессоры x86 имеют специальную инструкцию для этой функции, которая быстрее и точнее, чем этот хак. К сожалению,
1.0 / x.sqrt()
похоже, не оптимизировать под эту инструкцию . Так что, если вам действительно нужна скорость, возможно,_mm_rsqrt_ps
стоит использовать встроенные функции. Это, однако, снова требуетunsafe
кода. Я не буду вдаваться в подробности в этом ответе, так как на самом деле это нужно меньшинству программистов.источник
addss
илиmulss
. Но если другие 96 бит xmm0 можно игнорировать, тогда можно использоватьpsrld
инструкцию. То же самое касается целочисленного вычитания.fast_inv_sqrt
- всего лишь один шаг итерации Ньютона-Рафсона, чтобы найти лучшее приближениеinv_sqrt
. В этой части нет ничего опасного. Обман в первой части, которая находит хорошее приближение. Это работает, потому что это делает целочисленное деление на 2 в экспоненциальной части числа с плавающей точкой, и действительноsqrt(pow(0.5,x))=pow(0.5,x/2)
movd
в EAX и обратно - это пропущенная оптимизация текущих компиляторов. (И да, соглашения о вызовах передают / возвращают скалярfloat
в элементе low XMM и допускают, что старшие биты могут быть мусором. Но обратите внимание, что, если он был расширен с нуля, он может легко остаться таким: сдвиг вправо не приводит к ноль элементов и ни одно из них не вычитает из_mm_set_epi32(0,0,0,0x5f3759df)
, т. е.movd
загружает. Вам нужно былоmovdqa xmm1,xmm0
бы скопировать регистр раньшеpsrld
. Обойти задержку от FP, переадресация команды на целое число и наоборот скрыта заmulss
задержкойЭтот реализован с менее известным
union
в Rust:Сделал некоторые микро тесты, используя
criterion
crate на x86-64 Linux box. Удивительно, но у Рустаsqrt().recip()
самое быстрое. Но, конечно, любой результат микро-теста должен быть взят с зерном соли.источник
sqrt().inv()
, быстрее всех. И sqrt, и inv - это отдельные инструкции, и они идут довольно быстро. Doom был написан в те дни, когда было небезопасно предполагать, что аппаратная плавающая точка вообще существует, а трансцендентные функции, такие как sqrt, определенно были бы программными. +1 за отметки.transmute
видимому , отличаются отto_
иfrom_bits
- я бы ожидать тех научения эквивалента даже до оптимизации.Вы можете использовать,
std::mem::transmute
чтобы сделать необходимое преобразование:Вы можете посмотреть живой пример здесь: здесь
источник
f32::to_bits
иf32::from_bits
. Он также несет в себе цель, явно отличную от трансмутации, которую большинство людей, вероятно, считают «магией».unsafe
следует избегать, так как в этом нет необходимости.