我们知道如果n
不是一个完美的正方形,那么sqrt(n)
就不是一个整数。由于我只需要整数部分,我觉得调用sqrt(n)
不会那么快,因为计算小数部分也需要时间。
所以我的问题是,
我们是否只能获得 sqrt(n)的整数部分而不计算sqrt(n)
的实际值?该算法应该比sqrt(n)
(在<math.h>
或<cmath>
中定义)更快?
如果可能,您也可以在asm
块中编写代码。
答案 0 :(得分:21)
我会尝试Fast Inverse Square Root技巧。
这是一种非常好的近似1/sqrt(n)
而没有任何分支的方法,基于一些比特错误,因此不可移植(特别是在32位和64位平台之间)。
一旦得到它,你只需要反转结果,并取整数部分。
当然,可能会有更快的技巧,因为这个技巧有点过时了。
编辑:让我们这样做!
首先是一个小帮手:
// benchmark.h
#include <sys/time.h>
template <typename Func>
double benchmark(Func f, size_t iterations)
{
f();
timeval a, b;
gettimeofday(&a, 0);
for (; iterations --> 0;)
{
f();
}
gettimeofday(&b, 0);
return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) -
(a.tv_sec * (unsigned int)1e6 + a.tv_usec);
}
然后是主体:
#include <iostream>
#include <cmath>
#include "benchmark.h"
class Sqrt
{
public:
Sqrt(int n): _number(n) {}
int operator()() const
{
double d = _number;
return static_cast<int>(std::sqrt(d) + 0.5);
}
private:
int _number;
};
// http://www.codecodex.com/wiki/Calculate_an_integer_square_root
class IntSqrt
{
public:
IntSqrt(int n): _number(n) {}
int operator()() const
{
int remainder = _number;
if (remainder < 0) { return 0; }
int place = 1 <<(sizeof(int)*8 -2);
while (place > remainder) { place /= 4; }
int root = 0;
while (place)
{
if (remainder >= root + place)
{
remainder -= root + place;
root += place*2;
}
root /= 2;
place /= 4;
}
return root;
}
private:
int _number;
};
// http://en.wikipedia.org/wiki/Fast_inverse_square_root
class FastSqrt
{
public:
FastSqrt(int n): _number(n) {}
int operator()() const
{
float number = _number;
float x2 = number * 0.5F;
float y = number;
long i = *(long*)&y;
//i = (long)0x5fe6ec85e7de30da - (i >> 1);
i = 0x5f3759df - (i >> 1);
y = *(float*)&i;
y = y * (1.5F - (x2*y*y));
y = y * (1.5F - (x2*y*y)); // let's be precise
return static_cast<int>(1/y + 0.5f);
}
private:
int _number;
};
int main(int argc, char* argv[])
{
if (argc != 3) {
std::cerr << "Usage: %prog integer iterations\n";
return 1;
}
int n = atoi(argv[1]);
int it = atoi(argv[2]);
assert(Sqrt(n)() == IntSqrt(n)() &&
Sqrt(n)() == FastSqrt(n)() && "Different Roots!");
std::cout << "sqrt(" << n << ") = " << Sqrt(n)() << "\n";
double time = benchmark(Sqrt(n), it);
double intTime = benchmark(IntSqrt(n), it);
double fastTime = benchmark(FastSqrt(n), it);
std::cout << "Number iterations: " << it << "\n"
"Sqrt computation : " << time << "\n"
"Int computation : " << intTime << "\n"
"Fast computation : " << fastTime << "\n";
return 0;
}
结果:
sqrt(82) = 9
Number iterations: 4096
Sqrt computation : 56
Int computation : 217
Fast computation : 119
// Note had to tweak the program here as Int here returns -1 :/
sqrt(2147483647) = 46341 // real answer sqrt(2 147 483 647) = 46 340.95
Number iterations: 4096
Sqrt computation : 57
Int computation : 313
Fast computation : 119
如果符合预期,快速计算的性能远远优于 Int 计算。
哦,顺便说一句,sqrt
更快:)
答案 1 :(得分:16)
(int) sqrt(i)
使用正确的设置(-march=native -m64 -O3
)进行分析后,上面的内容更快批次。
好吧,有点老问题,但还没有给出“最快”的答案。最快的(我认为)是二进制平方根算法,完全在this Embedded.com article中解释。
基本上归结为:
unsigned short isqrt(unsigned long a) {
unsigned long rem = 0;
int root = 0;
int i;
for (i = 0; i < 16; i++) {
root <<= 1;
rem <<= 2;
rem += a >> 30;
a <<= 2;
if (root < rem) {
root++;
rem -= root;
root++;
}
}
return (unsigned short) (root >> 1);
}
在我的机器上(Q6600,Ubuntu 10.10)我通过取数字1-100000000的平方根进行分析。使用iqsrt(i)
花了2750毫秒。使用(unsigned short) sqrt((float) i)
需要3600毫秒。这是使用g++ -O3
完成的。使用-ffast-math
编译选项,时间分别为2100ms和3100ms。请注意,这甚至不使用单行汇编程序,因此它可能仍然会更快。
上述代码适用于C和C ++,并且对Java也进行了少量语法更改。
对于有限范围更有效的是二分搜索。在我的机器上,它将上面的版本从水中吹出4倍。可悲的是它的范围非常有限:
#include <stdint.h>
const uint16_t squares[] = {
0, 1, 4, 9,
16, 25, 36, 49,
64, 81, 100, 121,
144, 169, 196, 225,
256, 289, 324, 361,
400, 441, 484, 529,
576, 625, 676, 729,
784, 841, 900, 961,
1024, 1089, 1156, 1225,
1296, 1369, 1444, 1521,
1600, 1681, 1764, 1849,
1936, 2025, 2116, 2209,
2304, 2401, 2500, 2601,
2704, 2809, 2916, 3025,
3136, 3249, 3364, 3481,
3600, 3721, 3844, 3969,
4096, 4225, 4356, 4489,
4624, 4761, 4900, 5041,
5184, 5329, 5476, 5625,
5776, 5929, 6084, 6241,
6400, 6561, 6724, 6889,
7056, 7225, 7396, 7569,
7744, 7921, 8100, 8281,
8464, 8649, 8836, 9025,
9216, 9409, 9604, 9801,
10000, 10201, 10404, 10609,
10816, 11025, 11236, 11449,
11664, 11881, 12100, 12321,
12544, 12769, 12996, 13225,
13456, 13689, 13924, 14161,
14400, 14641, 14884, 15129,
15376, 15625, 15876, 16129,
16384, 16641, 16900, 17161,
17424, 17689, 17956, 18225,
18496, 18769, 19044, 19321,
19600, 19881, 20164, 20449,
20736, 21025, 21316, 21609,
21904, 22201, 22500, 22801,
23104, 23409, 23716, 24025,
24336, 24649, 24964, 25281,
25600, 25921, 26244, 26569,
26896, 27225, 27556, 27889,
28224, 28561, 28900, 29241,
29584, 29929, 30276, 30625,
30976, 31329, 31684, 32041,
32400, 32761, 33124, 33489,
33856, 34225, 34596, 34969,
35344, 35721, 36100, 36481,
36864, 37249, 37636, 38025,
38416, 38809, 39204, 39601,
40000, 40401, 40804, 41209,
41616, 42025, 42436, 42849,
43264, 43681, 44100, 44521,
44944, 45369, 45796, 46225,
46656, 47089, 47524, 47961,
48400, 48841, 49284, 49729,
50176, 50625, 51076, 51529,
51984, 52441, 52900, 53361,
53824, 54289, 54756, 55225,
55696, 56169, 56644, 57121,
57600, 58081, 58564, 59049,
59536, 60025, 60516, 61009,
61504, 62001, 62500, 63001,
63504, 64009, 64516, 65025
};
inline int isqrt(uint16_t x) {
const uint16_t *p = squares;
if (p[128] <= x) p += 128;
if (p[ 64] <= x) p += 64;
if (p[ 32] <= x) p += 32;
if (p[ 16] <= x) p += 16;
if (p[ 8] <= x) p += 8;
if (p[ 4] <= x) p += 4;
if (p[ 2] <= x) p += 2;
if (p[ 1] <= x) p += 1;
return p - squares;
}
可以在此处下载32位版本:https://gist.github.com/3481770
答案 2 :(得分:6)
虽然我怀疑你可以通过搜索“快速整数平方根”找到很多选项,但这里有一些可能很有效的新想法(每个都是独立的,或者你可以将它们结合起来):
static const
数组,并对其执行快速无分支二进制搜索。数组中生成的索引是平方根。答案 3 :(得分:6)
我认为Google search
提供了很好的文章,例如Calculate an integer square root
讨论了很多可能的快速计算方法,并且有很好的参考文章,我认为这里没有人可以比他们更好(如果有人可以首先制作关于它的论文),但是如果你阅读它们并且它们含糊不清,那么我们可以帮助你。
答案 4 :(得分:6)
如果你不介意近似,那么我拼凑在一起的整数sqrt函数怎么样。
int sqrti(int x)
{
union { float f; int x; } v;
// convert to float
v.f = (float)x;
// fast aprox sqrt
// assumes float is in IEEE 754 single precision format
// assumes int is 32 bits
// b = exponent bias
// m = number of mantissa bits
v.x -= 1 << 23; // subtract 2^m
v.x >>= 1; // divide by 2
v.x += 1 << 29; // add ((b + 1) / 2) * 2^m
// convert to int
return (int)v.f;
}
它使用此Wikipedia文章中描述的算法。 在我的机器上,它几乎是sqrt的两倍:)
答案 5 :(得分:4)
要做整数sqrt,你可以使用牛顿方法的这种专业化:
Def isqrt(N):
a = 1
b = N
while |a-b| > 1
b = N / a
a = (a + b) / 2
return a
基本上对于任何x,sqrt位于范围(x ... N / x)中,因此我们只是在每个循环中将该间隔平分为新猜测。有点像二进制搜索但它收敛必须更快。
这收敛于O(loglog(N)),这非常快。它根本不使用浮点数,它也适用于任意精度整数。
答案 6 :(得分:3)
为什么没有人建议最快的方法?
如果:
然后使用int[MAX_X]
创建sqrt(x)
填充(启动时)(您不需要使用函数sqrt()
)。
所有这些条件都很适合我的计划。
特别是,int[10000000]
数组将消耗40MB
。
您对此有何看法?
答案 7 :(得分:3)
这太短了,以至于99%的内联:
static inline int sqrtn(int num) {
int i;
__asm__ (
"pxor %%xmm0, %%xmm0\n\t" // clean xmm0 for cvtsi2ss
"cvtsi2ss %1, %%xmm0\n\t" // convert num to float, put it to xmm0
"sqrtss %%xmm0, %%xmm0\n\t" // square root xmm0
"cvttss2si %%xmm0, %0" // float to int
:"=r"(i):"r"(num):"%xmm0"); // i: result, num: input, xmm0: scratch register
return i;
}
为什么干净的xmm0
? cvtsi2ss
目标操作数是XMM寄存器。结果存储在目标操作数的低位双字中,而高位三个双字保持不变。
GCC内部版本(仅在GCC上运行):
#include <xmmintrin.h>
int sqrtn2(int num) {
register __v4sf xmm0 = {0, 0, 0, 0};
xmm0 = __builtin_ia32_cvtsi2ss(xmm0, num);
xmm0 = __builtin_ia32_sqrtss(xmm0);
return __builtin_ia32_cvttss2si(xmm0);
}
英特尔固有版本(已在GCC,Clang和ICC上测试):
#include <xmmintrin.h>
int sqrtn2(int num) {
register __m128 xmm0 = _mm_setzero_ps();
xmm0 = _mm_cvt_si2ss(xmm0, num);
xmm0 = _mm_sqrt_ss(xmm0);
return _mm_cvtt_ss2si(xmm0);
}
^^^^它们都需要SSE 1(甚至不需要SSE 2)。
答案 8 :(得分:2)
在许多情况下,甚至不需要精确的整数sqrt值,足以具有良好的近似值。 (例如,它通常发生在DSP优化中,当32位信号应压缩为16位或16位到8位时,不会在零点附近失去太多精度)。
我发现了这个有用的等式:
k = ceil(MSB(n)/2); - MSB(n) is the most significant bit of "n"
sqrt(n) ~= 2^(k-2)+(2^(k-1))*n/(2^(2*k))); - all multiplications and divisions here are very DSP-friendly, as they are only 2^k.
此公式生成平滑曲线(n,sqrt(n)),其值与实际sqrt(n)差别不大,因此在近似精度足够时非常有用。
答案 9 :(得分:1)
如果你需要计算平方根的性能,我想你会计算很多。 那为什么不缓解答案呢?在你的情况下我不知道N的范围,也不知道你将计算同一整数的平方根的很多倍,但如果是,那么你可以在每次调用方法时缓存结果(在数组中将是最有效的,如果不是太大)。
答案 10 :(得分:1)
在我的计算机上使用gcc,使用-ffast-math,将32位整数转换为float并使用sqrtf每10 ^ 9个操作需要1.2 s(没有-ffast-math需要3.54 s)。
以下算法每10 ^ 9使用0.87秒而牺牲一些准确性:尽管RMS误差仅为0.79,但误差可能高达-7或+1:
uint16_t SQRTTAB[65536];
inline uint16_t approxsqrt(uint32_t x) {
const uint32_t m1 = 0xff000000;
const uint32_t m2 = 0x00ff0000;
if (x&m1) {
return SQRTTAB[x>>16];
} else if (x&m2) {
return SQRTTAB[x>>8]>>4;
} else {
return SQRTTAB[x]>>8;
}
}
该表使用:
构建void maketable() {
for (int x=0; x<65536; x++) {
double v = x/65535.0;
v = sqrt(v);
int y = int(v*65535.0+0.999);
SQRTTAB[x] = y;
}
}
我发现使用进一步的if语句来改进二分法确实提高了准确性,但它也减慢了sqrtf更快的速度,至少使用-ffast-math。
答案 11 :(得分:1)
以下解决方案将计算整数部分,准确地表示floor(sqrt(x))
,没有舍入错误。
float
或double
既便携又不够精确isqrt
给出了类似isqrt(100) = 15
的疯狂结果sqrtf
我的地雷基于bit-guessing approach proposed on Wikipedia。不幸的是,维基百科上提供的伪代码存在一些错误,因此我不得不进行一些调整:
// C++20 also provides std::bit_width in its <bit> header
unsigned char bit_width(unsigned long long x) {
return x == 0 ? 1 : 64 - __builtin_clzll(x);
}
template <typename Int, std::enable_if_t<std::is_unsigned<Int, int = 0>>
Int sqrt(const Int n) {
unsigned char shift = bit_width(n);
shift += shift & 1; // round up to next multiple of 2
Int result = 0;
do {
shift -= 2;
result <<= 1; // make space for the next guessed bit
result |= 1; // guess that the next bit is 1
result ^= result * result > (n >> shift); // revert if guess too high
} while (shift != 0);
return result;
}
可以在恒定时间内对 bit_width
进行求值,循环最多重复ceil(bit_width / 2)
次。因此,即使对于64位整数,这也将是基本算术和按位运算的最坏32次迭代。
compile output仅约20条指令。
我通过统一生成输入来针对基于float
的方法进行基准测试。请注意,在现实世界中,大多数输入将比std::numeric_limits<...>::max()
更接近于零。
uint32_t
,这比使用25x
的效果要差std::sqrt(float)
uint64_t
,这比使用30x
的效果要差std::sqrt(double)
与使用浮点数学的方法不同,此方法始终非常准确。
sqrtf
可以在[2 28 ,2 32 )范围内提供错误的舍入。例如,sqrtf(0xffffffff) = 65536
的平方根实际上是65535.99999
。sqrt(0x3fff...) = 2147483648
的平方根实际上是2147483647.999999
。唯一涵盖所有64位整数的是x86扩展精度long double
,仅仅是因为它可以容纳整个64位整数。
正如我所说,这是正确处理所有输入,避免整数除法且不需要查找表的唯一解决方案。
总而言之,如果您需要一种独立于精度且不需要巨大查找表的方法,那么这是您唯一的选择。
在性能并不重要的constexpr
环境中,获得100%准确的结果可能更为重要。
从一个好的猜测开始,牛顿的方法可能很快。对于我们的猜测,我们将向下舍入到2的下一个幂并在恒定时间内计算平方根。对于任何2 x ,我们都可以使用2 x / 2 求平方根。
template <typename Int, std::enable_if_t<std::is_unsigned_v<Int>, int> = 0>
Int sqrt_guess(const Int n)
{
Int log2floor = bit_width(n) - 1;
// sqrt(x) is equivalent to pow(2, x / 2 = x >> 1)
// pow(2, x) is equivalent to 1 << x
return 1 << (log2floor >> 1);
}
请注意,这不完全是2 x / 2 ,因为我们在右移期间损失了一些精度。而是2 floor(x / 2)。
还要注意,sqrt_guess(0) = 1
实际上是避免在第一次迭代中被零除的必要条件:
template <typename Int, std::enable_if_t<std::is_unsigned_v<Int>, int> = 0>
Int sqrt_newton(const Int n)
{
Int a = sqrt_guess(n);
Int b = n;
// compute unsigned difference
while (std::max(a, b) - std::min(a, b) > 1) {
b = n / a;
a = (a + b) / 2;
}
// a is now either floor(sqrt(n)) or ceil(sqrt(n))
// we decrement in the latter case
// this is overflow-safe as long as we start with a lower bound guess
return a - (a * a > n);
}
这种替代方法的执行效果与第一个提议大致相同,但是通常快几个百分点。但是,它严重依赖高效的硬件划分,结果可能会有很大差异。
使用sqrt_guess
有很大的不同。这比使用1
作为初始猜测要快大约五倍。
答案 12 :(得分:1)
或者只是做一个二分查找,不能写一个更简单的版本imo:
uint16_t sqrti(uint32_t num)
{
uint16_t ret = 0;
for(int32_t i = 15; i >= 0; i--)
{
uint16_t temp = ret | (1 << i);
if(temp * temp <= num)
{
ret = temp;
}
}
return ret;
}