一如既往地感谢任何意见/建议:)
在以下两个小脚本中,我分别尝试while和for循环来计算100,000的阶乘.具有while循环的那个用了57分17秒完成,而for循环等效花了1小时7分54秒.
具有while循环的脚本:
use strict; use warnings; use bigint; my $now = time; my $n = shift; my $s = 1; while(1){ $s *= $n; $n--; last if $n==2; } print $s*$n; $now = time - $now; printf("\n\nTotal running time: %02d:%02d:%02d\n\n",int($now / 3600),int(($now % 3600) / 60),int($now % 60));
有for循环的脚本:
use strict; use warnings; use bigint; my $now = time; my $n =shift; my $s=1; for (my $i=2; $i<=$n;$i++) { $s = $s*$i; } print $s; $now = time - $now; printf("\n\nTotal running time: %02d:%02d:%02d\n\n",int($now % 60));
解决方法
while循环使用符号’$s * = $i’,但for循环使用’$s = $s * $i’.
很简单,证明这些不相同.此外,一个循环计数;另一个倒计时.这会影响要倍增的数字的大小.这是二阶效应 – 但并非完全可以忽略不计.
[更新:修改后只显示一个版本的代码,具有亚秒级时序.有空间认为打印应该从时间计算中排除;虽然让事情变得更加混乱,但我并没有打扰.我修复了以前版本中的错误:循环4与循环3相同 – 现在不是.我还调出了输出格式(尽管可以改进亚秒级处理 – 读者的练习),并且有更好的“进度报告”.
Mac Mini(Snow Leopard 10.6.2)的计时结果如下:
Count up $s *= $i: 00:00:12.663337 Count up $s = $s * $i: 00:00:20.686111 Count down $s *= $i: 00:00:14.201797 Count down $s = $s * $i: 00:00:23.269874
剧本:
use Time::HiRes qw(gettimeofday); use strict; use warnings; use bigint; use constant factorial_of => 13000; sub delta_t { my($tag,$t1,$t2) = @_; my($d) = int($t2 - $t1); my($f) = ($t2 - $t1) - $d; my($s) = sprintf("%.6f",$f); $s =~ s/^0//; printf "%-25s %02d:%02d:%02d%s\n",$tag,int($d/3600),int(($d % 3600) / 60),int($d % 60),$s; } my $t1 = gettimeofday; { my $n = factorial_of; my $s = 1; for (my $i = 2; $i <= $n; $i++) { $s *= $i; } print "$s\n: Loop 1\n"; } my $t2 = gettimeofday; delta_t('Count up $s *= $i:',$t2); { my $n = factorial_of; my $s = 1; for (my $i = 2; $i <= $n; $i++) { $s = $s * $i; } print "$s\n: Loop 2\n"; } my $t3 = gettimeofday; delta_t('Count up $s *= $i:',$t2); delta_t('Count up $s = $s * $i:',$t2,$t3); { my $n = factorial_of; my $s = 1; for (my $i = $n; $i > 1; $i--) { $s *= $i; } print "$s\n: Loop 3\n"; } my $t4 = gettimeofday; delta_t('Count up $s *= $i:',$t3); delta_t('Count down $s *= $i:',$t3,$t4); { my $n = factorial_of; my $s = 1; for (my $i = $n; $i > 1; $i--) { $s = $s * $i; } print "$s\n: Loop 4\n"; } my $t5 = gettimeofday; delta_t('Count up $s *= $i:',$t4); delta_t('Count down $s = $s * $i:',$t4,$t5);
这里是上面代码的一个更紧凑的版本,扩展到测试’while’循环以及’for’循环.它还涉及大多数时间问题.唯一不理想的东西(对我来说)是它使用了几个全局变量,并且我在代码refs中略微扫描代码,所以它都适合一行而不触发滚动条(无论如何,在我的显示器上) ).显然,通过更多的工作,测试可以被包装成一个数组,这样测试就可以迭代完成 – 一个循环通过数组运行定时器函数对数组中的信息.等等……它是一个SMOP – 简单的编程问题. (它打印了阶乘的MD5哈希值,而不是因子本身,因为它更容易比较结果等.它确实指出了一些错误,因为我正在重构上面的代码.是的,MD5不安全 – 但我不是为了安全而使用它;只是为了发现无意的变化.)
use Time::HiRes qw(gettimeofday); use Digest::MD5 qw(md5_hex); use strict; use warnings; use bigint; use constant factorial_of => 13000; my ($s,$i); my $l1 = sub {my($n) = @_; for ($i = 2; $i <= $n; $i++) { $s *= $i; }}; my $l2 = sub {my($n) = @_; for ($i = 2; $i <= $n; $i++) { $s = $s * $i; }}; my $l3 = sub {my($n) = @_; for ($i = $n; $i > 1; $i--) { $s *= $i; }}; my $l4 = sub {my($n) = @_; for ($i = $n; $i > 1; $i--) { $s = $s * $i; }}; my $l5 = sub {my($n) = @_; $i = 2; while ($i <= $n) { $s *= $i; $i++; }}; my $l6 = sub {my($n) = @_; $i = 2; while ($i <= $n) { $s = $s * $i; $i++; }}; my $l7 = sub {my($n) = @_; $i = $n; while ($i > 1) { $s *= $i; $i--; }}; my $l8 = sub {my($n) = @_; $i = $n; while ($i > 1) { $s = $s * $i; $i--; }}; sub timer { my($n,$code,$tag) = @_; my $t1 = gettimeofday; $s = 1; &$code(factorial_of); my $t2 = gettimeofday; my $md5 = md5_hex($s); printf "Loop %d: %-33s %09.6f (%s)\n",$n,$t2 - $t1,$md5; } my $count = 1; timer($count++,$l1,'for - Count up $s *= $i:'); timer($count++,$l2,'for - Count up $s = $s * $i:'); timer($count++,$l3,'for - Count down $s *= $i:'); timer($count++,$l4,'for - Count down $s = $s * $i:'); timer($count++,$l5,'while - Count up $s *= $i:'); timer($count++,$l6,'while - Count up $s = $s * $i:'); timer($count++,$l7,'while - Count down $s *= $i:'); timer($count++,$l8,'while - Count down $s = $s * $i:');
示例输出(压缩MD5校验和以避免换行 – 完整值为584b3ab832577fd1390970043efc0ec8):
Loop 1: for - Count up $s *= $i: 12.853630 (584b3ab8...3efc0ec8) Loop 2: for - Count up $s = $s * $i: 20.854735 (584b3ab8...3efc0ec8) Loop 3: for - Count down $s *= $i: 14.798155 (584b3ab8...3efc0ec8) Loop 4: for - Count down $s = $s * $i: 23.699913 (584b3ab8...3efc0ec8) Loop 5: while - Count up $s *= $i: 12.972428 (584b3ab8...3efc0ec8) Loop 6: while - Count up $s = $s * $i: 21.192956 (584b3ab8...3efc0ec8) Loop 7: while - Count down $s *= $i: 14.555620 (584b3ab8...3efc0ec8) Loop 8: while - Count down $s = $s * $i: 23.790795 (584b3ab8...3efc0ec8)
我一直在相应的’for’循环中看到’while’循环的小(<1%)惩罚,但我没有很好的解释.