我有一个算法,它基于使用有符号整数的最大公约数,所以我试图让它更快。请不要告诉我这种优化是不必要的,因为我已经将性能提高了50%。我没有看到任何潜在的优化,但我可能错了。
在下面的代码中,断言b!= 0
Integer gcd(Integer a, Integer b)
{
if ( a == 0 )
{
return b;
}
if ( a == b )
{
return a;
}
const Integer mask_a = (a >> (sizeof(Integer) * 8 - 1));
a = (a + mask_a) ^ mask_a; // a = |a|
const Integer mask_b = (b >> (sizeof(Integer) * 8 - 1));
b = (b + mask_b) ^ mask_b; // b = |b|
if ( ~a & 1 )
{
if ( b & 1 )
{
// 2 divides a but not b
return gcd(a >> 1, b);
}
else
{
// both a and b are divisible by 2, thus gcd(a, b) == 2 * gcd( a / 2, b / 2)
return gcd(a >> 1, b >> 1) << 1;
}
}
if ( ~b & 1 )
{
// 2 divides b, but not a
return gcd(a, b >> 1);
}
if ( a > b )
{
// since both a and b are odd, a - b is divisible by 2
// gcd(a, b) == gcd( a - b, b)
return gcd((a - b) >> 1, b);
}
else
{
// since both a and b are odd, a - b is divisible by 2
// gcd(a, b) == gcd( a - b, b)
return gcd((b - a) >> 1, a);
}
}
作为旁注:整数可以是int
,long
或long long
。它是上面的某个类型。
正如你所看到的那样,优化了将a和b带到各自的绝对值,这在我的机器上运行良好(不一定是所有的afaik)。我有点不喜欢分支的混乱。有没有办法改善它?
答案 0 :(得分:2)
我做了一些测试和这样的简单算法:
int gcd(int a,int b)
{
while(1)
{
int c = a%b;
if(c==0)
return abs(b);
a = b;
b = c;
}
}
比你的代码快5倍。
试试这个:
Integer gcd( Integer a, Integer b )
{
if( a == b )
return a;
if( a == 0 )
return b;
if( b == 0 )
return a;
while( b != 0 ) {
Integer t = b;
b = a % b;
a = t;
}
return a;
}
这是@ unkulunkulu的代码+你的IF在开始时。如果您的输入有很多微不足道的情况,它会更快。不幸的是,如果是这种情况,它将不会比你的代码快得多,你也无法做很多事情。
答案 1 :(得分:2)
另一种变体,带有移位查找表的二进制GCD,
typedef unsigned /* long long int */ UInteger;
Integer lookup(Integer a, Integer b) {
static const int lut[] =
{ 8, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 5, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 6, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 5, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 7, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 5, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 6, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 5, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
, 4, 0, 1, 0, 2, 0, 1, 0
, 3, 0, 1, 0, 2, 0, 1, 0
};
if (a == 0) return b;
const Integer mask_a = a >> (sizeof(Integer) * 8 - 1);
UInteger a1 = (a + mask_a) ^ mask_a;
const Integer mask_b = b >> (sizeof(Integer) * 8 - 1);
UInteger b1 = (b + mask_b) ^ mask_b;
int sa = 0, sb = 0, shift;
do {
shift = lut[a1 & 0xFF];
a1 >>= shift;
sa += shift;
}while(shift == 8);
do {
shift = lut[b1 & 0xFF];
b1 >>= shift;
sb += shift;
}while(shift == 8);
// sa holds the amount to shift the result by, the smaller of the trailing zeros counts
if (sa > sb) sa = sb;
// now a1 and b1 are both odd
if (a1 < b1) {
UInteger tmp = a1;
a1 = b1;
b1 = tmp;
}
while(b1 > 1) {
do {
a1 -= b1;
if (a1 == 0){
return (b1 << sa);
}
do {
a1 >>= (shift = lut[a1 & 0xFF]);
}while(shift == 8);
}while(b1 < a1);
do {
b1 -= a1;
if (b1 == 0){
return (a1 << sa);
}
do {
b1 >>= (shift = lut[b1 & 0xFF]);
}while(shift == 8);
}while(a1 < b1);
}
return (1 << sa);
}
在我的方框中,这比欧几里德算法快一点,这比递归二进制GCD要快得多,或者没有用于移位的查找表。具有16个元素查找表的版本比Euclid稍慢。
但是,如果您的输入非常小,正如您在评论中所述,计算GCD本身的查找表可能会更快,至少对于小a
和b
而言(比如说0 <= a,b <= 50
),只有在输入大于查找表允许的情况下才会回退到计算中。
答案 2 :(得分:1)
您可以通过用循环替换递归来优化它。即使你的编译器优化了对跳转的尾递归调用,你仍然会在递归调用中对a == 0
和绝对值计算进行不必要的检查。
要处理非尾递归gcd(a >> 1, b >> 1) << 1
,你必须引入一个从零开始的累加器变量,用于在return
之前左移结果。
示例(未经测试):
Integer gcd(Integer a, Integer b)
{
const Integer mask_a = a >> (sizeof(Integer) * 8 - 1);
a = (a + mask_a) ^ mask_a;
const Integer mask_b = b >> (sizeof(Integer) * 8 - 1);
b = (b + mask_b) ^ mask_b;
int shift = 0;
while (a != 0 && a != b) {
if (~a & 1) {
a >>= 1;
if (!(b & 1)) {
b >>= 1;
shift++;
}
} else if (~b & 1) {
b >>= 1;
} else if (a > b) {
a = (a - b) >> 1;
} else {
b = (b - a) >> 1; // the error was here and i have to write 6 chars about it, formerly it was a = (b - a) >> 1;
}
}
return b << shift;
}
答案 3 :(得分:1)
如果您的编译器支持它,您可以为每个likely()
使用unlikely()
或if()
等内置函数,这将允许编译器进行更多优化。
这些功能在Linux内核中定义。使用gcc,它将替换为__builtin_expect()
。