2010-03-20 52 views
6

我做了一個小實驗,如下所示,它看起來像while循環比Perl中的for循環更快。但是由於這個實驗比較粗糙,主題可能比看起來要複雜得多,所以我想聽聽你對此有何評論。 一如既往的感謝您的任何意見和建議:)在Perl中,while循環通常比for循環快嗎?

在以下兩個小腳本中,我嘗試了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)); 

腳本:

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/3600), 
      int(($now % 3600)/60), int($now % 60)); 
+3

你可能試圖優化錯誤的東西。我想知道你爲什麼認爲這部分語言如此重要? – Ether 2010-03-20 04:48:17

+0

每次運行它們幾次,它們的平均值可能會大致相同 – CaffGeek 2010-03-20 05:03:36

+0

@Chad,實際上我已經測試了幾次代碼。他們花了不同的時間完成同樣的工作。我認爲@Jonathan Leffler對插圖代碼的解釋非常有意義。 – Mike 2010-03-20 05:41:45

回答

8

這些循環並不等同,您主要是在抖動bigint包,它本身與for vs while無關。

while循環使用符號'$s *= $i',但for循環使用'$s = $s * $i'。 這很簡單,足以證明這些不相同。另外,一個循環計數;另一個倒數。這會影響乘數的大小。這是二階效應 - 但不是完全可以忽略的。

[更新:修改爲僅顯示代碼的一個版本,並具有亞秒的時間。有時候可以認爲打印應該從時間計算中排除;這讓事情變得更加混亂,所以我沒有打擾過。我已修復了以前版本中的錯誤:循環4與循環3相同 - 現在不是。我也對輸出格式進行了調整(儘管可以改進次秒處理 - 這是讀者的一項練習),並且有更好的「進度報告」。]

在一臺Mac Mini(雪豹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:',  $t1, $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:',  $t1, $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:',  $t1, $t2); 
delta_t('Count up $s = $s * $i:', $t2, $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:',  $t1, $t2); 
delta_t('Count up $s = $s * $i:', $t2, $t3); 
delta_t('Count down $s *= $i:',  $t3, $t4); 
delta_t('Count down $s = $s * $i:', $t4, $t5); 

這裏還有一個更緊湊版本上面的代碼擴展爲測試'while'循環以及'for'循環。它也處理大部分時間問題。唯一不理想的是(它)使用了一些全局變量,並且我稍微在代碼中引用了代碼,因此它全部適合一行而不觸發滾動條(反正在我的顯示器上)。顯然,只需要多做一些工作,就可以將測試包裝到一個數組中,以便迭代地完成測試 - 通過對數組中的信息運行計時器函數的數組進行循環。等等...這是一個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, $tag, $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%),但我沒有很好的解釋它。

+0

@Jonathan Leffler,非常感謝!你的插圖代碼對我來說非常有啓發性。謝謝:) – Mike 2010-03-20 05:34:40

+0

@Joanthan,感謝您更新的代碼。我一直認爲$ s * = $ i'和'$ s = $ s * $ i',$ i ++和$ i--以不同的方式做同樣的事情但是我錯了。非常感謝您指出這一點:)我現在已經改變了,而對於腳本,現在我得到了: my $ now = time; my $ n = shift; my $ i = 2; my $ s = 1;對於(; $ i <= $ n; $ i ++){ $ s * = $ i; } And my $ n = shift; my $ i = 2; my $ s = 1; while($ i <= $ n){ $ s * = $ n; $ i ++; } 它們看起來很相似。結果:雖然速度更快。我不確定,但是我的實驗設計有什麼問題嗎?我運行了你的代碼,結果雖然慢了點。 – Mike 2010-03-21 12:34:24

+1

@Mike:對剩下的問題我沒什麼好感。主要的觀點是(1)問題主要是'bigint',(2)殘差'vs'與'差異'深藏在Perl字節代碼中。我在時間上有一些變化 - 主要是大約0.1秒左右,除非還有一個備份運行(TimeMachine到TimeCapsule);我選擇了13000作爲我的測試編號,以獲得一個足夠大的數字以獲得合理的時間,同時又不至於讓運行測試變得不舒服(例如1小時太長)。 – 2010-03-21 15:24:11

5

我會倒下震驚,如果確實存在,而之間的循環任何「真正」的區別。假設他們正在做「完全一樣」的事情,他們應該被解釋者優化成或多或少相同。

我敢打賭,這種差異可能只不過是在兩次執行期間爲資源爭奪不同的其他進程罷了。

即使存在差異,也不要陷入The Sad Tragedy of Micro-Optimization Theater

+0

@Morinar,我剛寫完你提出的文章。我明白你的觀點,謝謝。 – Mike 2010-03-20 05:38:57

5

基準測試的一個關鍵是簡化。提出的問題是forwhile的速度。但是實驗涉及幾個不必要的複雜性。

  • 這兩個循環並不像它們可能的那樣相似。一個使用$s *= $n,另一個使用$s = $s * $i(正如Jonathan Leffler指出的那樣)。一個使用$n--,另一個使用$i++(誰知道他們的速度是否不同?)。

  • 如果我們對forwhile感興趣,則不需要涉及bigint。這隻會混淆這個話題。特別是,您的while腳本僅取決於一個bigint對象($s),而for腳本使用其中兩個($s$i)。 for腳本比較慢,這並不讓我感到驚訝。

重寫你的循環,以儘可能相似,保持階乘足夠小,這樣你就不必使用bigint,並使用Benchmark模塊。然後,您可以進行一場公平的比賽,即forwhile。我會很好奇,看看你找到了什麼。

+1

@FM,我的實驗設計得很差,以至於我對結果的推斷與我發佈的問題幾乎完全無關。這完全是失敗。好吧,無論如何感謝給我留下這些有益的評論。看起來我總是可以從你們身上學到一兩件東西:) – Mike 2010-03-21 12:47:03

+1

@Mike不要對自己太強悍。標杆管理是棘手的,甚至有經驗的程序員在設置時都會犯錯誤。例如:http://stackoverflow.com/questions/1083269/is-perls-unpack-ever-faster-than-substr和http://stackoverflow.com/questions/1960779/why-does-perls-tr-n -get-慢 - 和 - 慢作爲線 - 長度 - 增加。你的基準可能有缺陷,但問題是成功的,因爲你學到了一些有用的東西。 :) – FMc 2010-03-21 13:50:33