ARMv5TE处理器提供了快速的整数乘法器和“计算前导零”指令。它们通常带有适度大小的缓存。基于此,实现高性能最合适的方法似乎是使用表查找进行初始近似,然后使用两个牛顿拉弗森迭代来实现完全精确的结果。我们可以通过将额外的预计算纳入表中来进一步加速这些迭代,这是Cray计算机四十年前使用的一种技术。
下面的函数
fxrsqrt()
实现了这种方法。它以8位近似值
r
为开始,用每个表元素存储3r(在32位条目的低10位中)和r的立方(在32位条目的上22位中),而不是存储
r
。这允许快速计算第一次迭代,如下所示:r
1 = 0.5 * (3 * r - a * r
3)。然后按照常规方式计算第二次迭代,r
2 = 0.5 * r
1 * (3 - r
1 * (r
1 * a))。
为了能够准确地执行这些计算,无论输入的大小如何,参数
a
在计算开始时进行了归一化,实质上将其表示为与2
scal乘以比例因子的
2.32
定点数。在计算结束时,根据公式1/sqrt(2
2n) = 2
-n对结果进行非标准化。通过将最高位被舍弃的位为1的结果四舍五入,提高了精度,从而几乎所有结果都被正确地舍入。详尽的测试报告如下:
results too low: 639 too high: 1454 not correctly rounded: 2093
。
该代码使用了两个辅助函数:
__clz()
确定一个非零32位参数中前导0位的数量。
__umulhi()
计算两个无符号32位整数的完整64位乘积的32位最高位。这两个函数应通过编译器内置函数或使用一些内联汇编来实现。在下面的代码中,我展示了适用于ARM CPU的可移植实现以及适用于x86平台的内联汇编版本。在ARMv5TE平台上,
__clz()
应映射到
CLZ
指令,
__umulhi()
应映射到
UMULL
。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#define USE_OWN_INTRINSICS 1
#if USE_OWN_INTRINSICS
__forceinline int __clz (uint32_t a)
{
int r;
__asm__ ("bsrl %1,%0\n\t" : "=r"(r): "r"(a));
return 31 - r;
}
uint32_t __umulhi (uint32_t a, uint32_t b)
{
uint32_t r;
__asm__ ("movl %1,%%eax\n\tmull %2\n\tmovl %%edx,%0\n\t"
: "=r"(r) : "r"(a), "r"(b) : "eax", "edx");
return r;
}
#else
int __clz (uint32_t a)
{
uint32_t r = 32;
if (a >= 0x00010000) { a >>= 16; r -= 16; }
if (a >= 0x00000100) { a >>= 8; r -= 8; }
if (a >= 0x00000010) { a >>= 4; r -= 4; }
if (a >= 0x00000004) { a >>= 2; r -= 2; }
r -= a - (a & (a >> 1));
return r;
}
uint32_t __umulhi (uint32_t a, uint32_t b)
{
return (uint32_t)(((uint64_t)a * b) >> 32);
}
#endif
uint32_t rsqrt_tab [96] =
{
0xfa0bdefa, 0xee6af6ee, 0xe5effae5, 0xdaf27ad9,
0xd2eff6d0, 0xc890aec4, 0xc10366bb, 0xb9a71ab2,
0xb4da2eac, 0xadce7ea3, 0xa6f2b29a, 0xa279a694,
0x9beb568b, 0x97a5c685, 0x9163027c, 0x8d4fd276,
0x89501e70, 0x8563da6a, 0x818ac664, 0x7dc4fe5e,
0x7a122258, 0x7671be52, 0x72e44a4c, 0x6f68fa46,
0x6db22a43, 0x6a52623d, 0x67041a37, 0x65639634,
0x622ffe2e, 0x609cba2b, 0x5d837e25, 0x5bfcfe22,
0x58fd461c, 0x57838619, 0x560e1216, 0x53300a10,
0x51c72e0d, 0x50621a0a, 0x4da48204, 0x4c4c2e01,
0x4af789fe, 0x49a689fb, 0x485a11f8, 0x4710f9f5,
0x45cc2df2, 0x448b4def, 0x421505e9, 0x40df5de6,
0x3fadc5e3, 0x3e7fe1e0, 0x3d55c9dd, 0x3d55d9dd,
0x3c2f41da, 0x39edd9d4, 0x39edc1d4, 0x38d281d1,
0x37bae1ce, 0x36a6c1cb, 0x3595d5c8, 0x3488f1c5,
0x3488fdc5, 0x337fbdc2, 0x3279ddbf, 0x317749bc,
0x307831b9, 0x307879b9, 0x2f7d01b6, 0x2e84ddb3,
0x2d9005b0, 0x2d9015b0, 0x2c9ec1ad, 0x2bb0a1aa,
0x2bb0f5aa, 0x2ac615a7, 0x29ded1a4, 0x29dec9a4,
0x28fabda1, 0x2819e99e, 0x2819ed9e, 0x273c3d9b,
0x273c359b, 0x2661dd98, 0x258ad195, 0x258af195,
0x24b71192, 0x24b6b192, 0x23e6058f, 0x2318118c,
0x2318718c, 0x224da189, 0x224dd989, 0x21860d86,
0x21862586, 0x20c19183, 0x20c1b183, 0x20001580
};
uint32_t fxrsqrt (uint32_t a)
{
uint32_t s, r, t, scal;
if (a == 0) return ~a;
scal = __clz (a) & 0xfffffffe;
a = a << scal;
t = rsqrt_tab [(a >> 25) - 32];
r = (t << 22) - __umulhi (t, a);
s = __umulhi (r, a);
s = 0x30000000 - __umulhi (r, s);
r = __umulhi (r, s);
r = ((r >> (18 - (scal >> 1))) + 1) >> 1;
return r;
}
uint32_t ref_fxrsqrt (uint32_t a)
{
double arg = a / 65536.0;
double rsq = sqrt (1.0 / arg);
uint32_t r = (uint32_t)(rsq * 65536.0 + 0.5);
return r;
}
int main (void)
{
uint32_t arg = 0x00000001;
uint32_t res, ref;
uint32_t err, lo = 0, hi = 0;
do {
res = fxrsqrt (arg);
ref = ref_fxrsqrt (arg);
err = 0;
if (res < ref) {
err = ref - res;
lo++;
}
if (res > ref) {
err = res - ref;
hi++;
}
if (err > 1) {
printf ("!!!! arg=%08x res=%08x ref=%08x\n", arg, res, ref);
return EXIT_FAILURE;
}
arg++;
} while (arg);
printf ("results too low: %u too high: %u not correctly rounded: %u\n",
lo, hi, lo + hi);
return EXIT_SUCCESS;
}