Performance 快速找到两个数字的GCD

Performance 快速找到两个数字的GCD,performance,perl,greatest-common-divisor,Performance,Perl,Greatest Common Divisor,有没有办法让这个程序更快?我正在考虑一些更快的用户输入工具等 这是我的密码: sub partia { my ( $u, $v ) = @_; if ( $u == $v ) { return $u } if ( $u == 0 ) { return $v } if ( $v == 0 ) { return $u } if ( ~$u & 1 ) { if ( $v & 1 ) { return

有没有办法让这个程序更快?我正在考虑一些更快的用户输入工具等

这是我的密码:

sub partia {
    my ( $u, $v ) = @_;
    if ( $u == $v ) { return $u }
    if ( $u == 0 )  { return $v }
    if ( $v == 0 )  { return $u }
    if ( ~$u & 1 ) {
        if ( $v & 1 ) {
            return partia( $u >> 1, $v );
        }
        else {
            return partia( $u >> 1, $v >> 1 ) << 1;
        }
    }

    if ( ~$v & 1 ) {
        return partia( $u, $v >> 1 );
    }

    if ( $u > $v ) {
        return partia( ( $u - $v ) >> 1, $v );
    }
    return partia( ( $v - $u ) >> 1, $u );
}

sub calosc {
    $t = <>;
    while ($t) {
        @tab = split( /\s+/, <> );
        print( partia( $tab[0], $tab[1] ), "\n" );
        $t--;
    }
}
calosc();
而不是

t=input() 

有什么方法可以做到吗?

除非我完全忘记我在做什么(没有承诺)-这个算法看起来像是在每次递归中将它的项除以2,这意味着你的算法是O(log-base2-N)。除非你能找到一个常数时间算法,否则你现在可能已经找到了最好的算法


现在@ikegami已经提到了微优化……如果你想做这些,我建议你去看看一个很棒的Perl分析器,它应该能够告诉你在算法中花了多少时间,这样你就可以瞄准你的微优化。

除非我完全忘了我在做什么(没有承诺)-此算法看起来像是在每次递归中将其项除以2,这意味着您的算法是O(log-base2-N)。除非你能找到一个常数时间算法,否则你现在可能已经找到了最好的算法

现在@ikegami已经提到了微优化……如果您想实现这些,我建议您查看一个很棒的Perl分析器,它应该能够告诉您在算法中花费的时间,这样您就可以针对您的微优化。

您的解决方案-递归Stein算法 看起来你只是想得到两个数字的最大值,并且想尽快做到这一点

显然,您使用的是的递归版本。通常来说,在速度和可伸缩性方面使用迭代算法要好得多。然而,我敢断言,首先尝试更简单的方法几乎肯定是值得的

备选方案-迭代Stein算法和基本欧几里德算法 我已经修改了您的脚本,从
\uuu数据块中选取3个数字对作为输入。第一对只是两个小数字,然后我从中得到两个数字,最后是两个较大的数字,包括一些二的共享幂

然后我编写了两个新的子程序。其中一个使用迭代Stein算法(您使用的方法),另一个只是简单的欧几里德算法。您的
partia
子例程与我的两个子例程进行了100万次迭代,结果表明迭代速度快了50%,欧几里德的速度快了3倍

use strict;
use warnings;

use Benchmark;
#use Math::Prime::Util::GMP qw(gcd);

# Original solution
# - Stein's Algorithm (recursive)
sub partia {
    my ( $u, $v ) = @_;
    if ( $u == $v ) { return $u }
    if ( $u == 0 )  { return $v }
    if ( $v == 0 )  { return $u }
    if ( ~$u & 1 ) {
        if ( $v & 1 ) {
            return partia( $u >> 1, $v );
        }
        else {
            return partia( $u >> 1, $v >> 1 ) << 1;
        }
    }

    if ( ~$v & 1 ) {
        return partia( $u, $v >> 1 );
    }

    if ( $u > $v ) {
        return partia( ( $u - $v ) >> 1, $v );
    }
    return partia( ( $v - $u ) >> 1, $u );
}

# Using Euclidian Algorithm
sub euclid {
    my ( $quotient, $divisor ) = @_;

    return $divisor if $quotient == 0;
    return $quotient if $divisor == 0;

    while () {
        my $remainder = $quotient % $divisor;
        return $divisor if $remainder == 0;
        $quotient = $divisor;
        $divisor = $remainder;
    }
}

# Stein's Algorithm (Iterative)
sub stein {
    my ($u, $v) = @_;

    # GCD(0,v) == v; GCD(u,0) == u, GCD(0,0) == 0
    return $v if $u == 0;
    return $u if $v == 0;

    # Remove all powers of 2 shared by U and V
    my $twos = 0;
    while ((($u | $v) & 1) == 0) {
        $u >>= 1;
        $v >>= 1;
        ++$twos;
    }

    # Remove Extra powers of 2 from U.  From here on, U is always odd.
    $u >>= 1 while ($u & 1) == 0;

    do {
        # Remove all factors of 2 in V -- they are not common 
        # Note: V is not zero, so while will terminate
        $v >>= 1 while ($v & 1) == 0;

        # Now U and V are both odd. Swap if necessary so U <= V,
        # then set V = V - U (which is even). For bignums, the
        # swapping is just pointer movement, and the subtraction
        # can be done in-place.
        ($u, $v) = ($v, $u) if $u > $v;

        $v -= $u;
    } while ($v != 0);

    return $u << $twos;
}

# Process 3 pairs of numbers
my @nums;
while (<DATA>) {
    my ($num1, $num2) = split;
#    print "Numbers = $num1, $num2\n";
#    print ' partia = ', partia($num1, $num2), "\n";
#    print ' euclid = ', euclid($num1, $num2), "\n";
#    print ' stein  = ', stein($num1, $num2), "\n";
#    print ' gcd    = ', gcd($num1, $num2), "\n\n";
    push @nums, [$num1, $num2];
}

# Benchmark!
timethese(1_000_000, {
    'Partia' => sub { partia(@$_) for @nums },
    'Euclid' => sub { euclid(@$_) for @nums },
    'Stein'  => sub { stein(@$_) for @nums },
#    'GCD'    => sub { gcd(@$_) for @nums },
});

__DATA__
20 25            # GCD of 5
89 144           # GCD of Fibonacci numbers = 1
4789084 957196   # GCD of 388 = 97 * 2 * 2
模块解决方案-数学::素数::Util::GMP qw(gcd) 不过,最快的解决方案可能是这些算法的C实现。因此,我建议找到像提供的那样已经编码的版本

运行包括此新函数在内的基准测试表明,它的速度是我编程的基本欧几里德算法的两倍:

Benchmark: timing 1000000 iterations of Euclid, GCD, Partia, Stein...
    Euclid:  8 wallclock secs ( 8.32 usr +  0.00 sys =  8.32 CPU) @ 120264.58/s (n=1000000)
       GCD:  3 wallclock secs ( 3.93 usr +  0.00 sys =  3.93 CPU) @ 254388.20/s (n=1000000)
    Partia: 26 wallclock secs (25.94 usr +  0.00 sys = 25.94 CPU) @ 38546.04/s (n=1000000)
     Stein: 18 wallclock secs (17.55 usr +  0.00 sys = 17.55 CPU) @ 56976.81/s (n=1000000)
您的解决方案-递归Stein算法 看起来你只是想得到两个数字的最大值,并且想尽快做到这一点

显然,您使用的是的递归版本。通常来说,在速度和可伸缩性方面使用迭代算法要好得多。然而,我敢断言,首先尝试更简单的方法几乎肯定是值得的

备选方案-迭代Stein算法和基本欧几里德算法 我已经修改了您的脚本,从
\uuu数据块中选取3个数字对作为输入。第一对只是两个小数字,然后我从中得到两个数字,最后是两个较大的数字,包括一些二的共享幂

然后我编写了两个新的子程序。其中一个使用迭代Stein算法(您使用的方法),另一个只是简单的欧几里德算法。您的
partia
子例程与我的两个子例程进行了100万次迭代,结果表明迭代速度快了50%,欧几里德的速度快了3倍

use strict;
use warnings;

use Benchmark;
#use Math::Prime::Util::GMP qw(gcd);

# Original solution
# - Stein's Algorithm (recursive)
sub partia {
    my ( $u, $v ) = @_;
    if ( $u == $v ) { return $u }
    if ( $u == 0 )  { return $v }
    if ( $v == 0 )  { return $u }
    if ( ~$u & 1 ) {
        if ( $v & 1 ) {
            return partia( $u >> 1, $v );
        }
        else {
            return partia( $u >> 1, $v >> 1 ) << 1;
        }
    }

    if ( ~$v & 1 ) {
        return partia( $u, $v >> 1 );
    }

    if ( $u > $v ) {
        return partia( ( $u - $v ) >> 1, $v );
    }
    return partia( ( $v - $u ) >> 1, $u );
}

# Using Euclidian Algorithm
sub euclid {
    my ( $quotient, $divisor ) = @_;

    return $divisor if $quotient == 0;
    return $quotient if $divisor == 0;

    while () {
        my $remainder = $quotient % $divisor;
        return $divisor if $remainder == 0;
        $quotient = $divisor;
        $divisor = $remainder;
    }
}

# Stein's Algorithm (Iterative)
sub stein {
    my ($u, $v) = @_;

    # GCD(0,v) == v; GCD(u,0) == u, GCD(0,0) == 0
    return $v if $u == 0;
    return $u if $v == 0;

    # Remove all powers of 2 shared by U and V
    my $twos = 0;
    while ((($u | $v) & 1) == 0) {
        $u >>= 1;
        $v >>= 1;
        ++$twos;
    }

    # Remove Extra powers of 2 from U.  From here on, U is always odd.
    $u >>= 1 while ($u & 1) == 0;

    do {
        # Remove all factors of 2 in V -- they are not common 
        # Note: V is not zero, so while will terminate
        $v >>= 1 while ($v & 1) == 0;

        # Now U and V are both odd. Swap if necessary so U <= V,
        # then set V = V - U (which is even). For bignums, the
        # swapping is just pointer movement, and the subtraction
        # can be done in-place.
        ($u, $v) = ($v, $u) if $u > $v;

        $v -= $u;
    } while ($v != 0);

    return $u << $twos;
}

# Process 3 pairs of numbers
my @nums;
while (<DATA>) {
    my ($num1, $num2) = split;
#    print "Numbers = $num1, $num2\n";
#    print ' partia = ', partia($num1, $num2), "\n";
#    print ' euclid = ', euclid($num1, $num2), "\n";
#    print ' stein  = ', stein($num1, $num2), "\n";
#    print ' gcd    = ', gcd($num1, $num2), "\n\n";
    push @nums, [$num1, $num2];
}

# Benchmark!
timethese(1_000_000, {
    'Partia' => sub { partia(@$_) for @nums },
    'Euclid' => sub { euclid(@$_) for @nums },
    'Stein'  => sub { stein(@$_) for @nums },
#    'GCD'    => sub { gcd(@$_) for @nums },
});

__DATA__
20 25            # GCD of 5
89 144           # GCD of Fibonacci numbers = 1
4789084 957196   # GCD of 388 = 97 * 2 * 2
模块解决方案-数学::素数::Util::GMP qw(gcd) 不过,最快的解决方案可能是这些算法的C实现。因此,我建议找到像提供的那样已经编码的版本

运行包括此新函数在内的基准测试表明,它的速度是我编程的基本欧几里德算法的两倍:

Benchmark: timing 1000000 iterations of Euclid, GCD, Partia, Stein...
    Euclid:  8 wallclock secs ( 8.32 usr +  0.00 sys =  8.32 CPU) @ 120264.58/s (n=1000000)
       GCD:  3 wallclock secs ( 3.93 usr +  0.00 sys =  3.93 CPU) @ 254388.20/s (n=1000000)
    Partia: 26 wallclock secs (25.94 usr +  0.00 sys = 25.94 CPU) @ 38546.04/s (n=1000000)
     Stein: 18 wallclock secs (17.55 usr +  0.00 sys = 17.55 CPU) @ 56976.81/s (n=1000000)

否则,您将看到微观优化。减少Perl操作的数量是好的。我看不到任何明显的诀窍,尽管知道算法的人可能会重新实现。子调用代价很高,所以我首先要摆脱完全无用的递归。当然,C实现将是实现永久性的途径。哦,不是完全无用的。有一个地方不是尾部递归的。不过很容易解决。清理了并删除了递归。这些更改是为了可读性而不是速度,但我不认为我在任何路径上添加了任何操作。这只是意味着可以取消更多的行动。与转换为C实现相比,节省的成本将是可怜的。Wikipedia已经提供了一个交互式版本,我已经将其转换为Perl。Oops缺少一个标志。否则,您将看到微观优化。减少Perl操作的数量是好的。我看不到任何明显的诀窍,尽管知道算法的人可能会重新实现。子调用代价很高,所以我首先要摆脱完全无用的递归。当然,C实现将是实现永久性的途径。哦,不是完全无用的。有一个地方不是尾部递归的。不过很容易解决。清理了并删除了递归。这些更改是为了可读性而不是速度,但我不认为我在任何路径上添加了任何操作。这只是意味着可以取消更多的行动。与转换为C实现相比,节省的成本将是可怜的。Wikipedia已经提供了一个交互式版本,我已经将其转换为Perl。Oops缺少一个标志。谢谢,这非常有用。Nit:log2(N)=logX(N)/log2(X),所以O(log2(N))=O(logX(N)