
为什么不用 1.0f / sqrtf(x) 而要手写位运算倒数平方根?
因为图形管线里每帧要算成千上万个 1.0f / sqrtf(x),而标准库的 sqrtf 是 IEEE 754 完全精度实现,耗时高;GPU 早期用的 rsqrt 指令本质就是这个位运算 trick 的硬件化。它不求精确值,只保证相对误差 sqrtf 的 3–5 倍。
核心思路:把 float 当作整数读出来,做一次位移和减法,再转回 float,最后用牛顿迭代修正一次 —— 这就是著名的 “Quake III Arena” 里的 Q_rsqrt。
- 只适用于
float(32 位),double不行 —— 位模式长度和 magic number 都不同 - 输入必须 > 0,且不能是 denormal 数(如
1e-40f),否则整数 reinterpret 会崩 - magic number
0x5f3759df是对函数f(y) = y⁻¹ᐟ²在 log-space 上的最优仿射逼近结果,不是随便写的
Q_rsqrt 的标准实现与关键参数解释
这是最稳定、被验证过的版本,直接可用:
float Q_rsqrt(float number) {
long i;
float x2, y;
const float threehalfs = 1.5F;
<pre class='brush:php;toolbar:false;'>x2 = number * 0.5F;
y = number;
i = *(long*)&y; // 把 float 的内存按 long 解释
i = 0x5f3759df - (i >> 1); // 关键位运算:右移1位相当于指数/2,再用 magic 减
y = *(float*)&i; // 转回 float,此时 y ≈ 1/sqrt(number)
y = y * (threehalfs - (x2 * y * y)); // 一次牛顿迭代:y ← y × (1.5 − x/2 × y²)
return y;}
立即学习“C++免费学习笔记(深入)”;
注意几个硬约束:
-
*(long*)&y依赖小端 +sizeof(long) == 4,在 64 位 Linux/macOS 上long可能是 8 字节 → 必须改用int32_t或std::bit_cast(C++20) - 右移用的是算术右移还是逻辑右移?对负数有影响,但这里
y > 0,最高位是符号位=0,所以>> 1安全 - 牛顿迭代只做一次:够用且性价比最高;两次虽更准,但开销翻倍,实际图形渲染中完全没必要
现代 C++ 中怎么安全又高效地写?别碰指针别踩 strict aliasing
直接类型双关(*(long*)&y)触发 undefined behavior(UB),GCC/Clang -O2 下可能优化出错。正确做法:
- 用
std::bit_cast<int32_t>(y)(C++20)—— 最干净,无 UB,编译器通常生成相同汇编 - 用
memcpy(&i, &y, sizeof(i))—— C++11 起就安全,主流编译器会优化成 mov 指令 - 绝对不要用 union hack,C++ 标准未保证 active member 切换后旧成员的 bit 表示仍有效
示例(C++20):
#include <bit>
float fast_rsqrt(float x) {
auto i = std::bit_cast<int32_t>(x);
i = 0x5f3759df - (i >> 1);
float y = std::bit_cast<float>(i);
return y * (1.5f - (x * 0.5f) * y * y);
}哪些场景下这算法会失效或变慢?
它不是万能加速器,用错地方反而拖累性能:
- 输入接近 0(比如
1e-38f)→ 对应的指数域极小,右移后 magic 减法溢出,结果为 NaN 或极大数 - 编译器开了
-ffast-math时,sqrtf本身可能已被替换成近似指令,此时Q_rsqrt优势消失,甚至因额外分支/指令更多而更慢 - 在 ARM64 或现代 x86-64 上,
rsqrtps指令延迟仅 3–4 cycle,比手写 C++ 版本还快 —— 直接内联汇编或用__builtin_ia32_rsqrtps更合适 - 如果只需要一次倒数平方根,且不处于 tight loop 中,老老实实用
1.0f / sqrtf(x),可读性、可维护性、跨平台性都更好
真正值得手写的,是那种每帧调用十万次以上、输入范围可控(如单位向量归一化)、且 profiling 确认它是瓶颈的内层循环。









