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)