Perl 的標竿測試(Benchmark)模組可以讓程式設計者很方便的測量程式的執行時間,本文將介紹這個模組的使用方式。
一般所謂的標竿測試(Benchmark)就是在特定的環境中,測試程式的執行效能,常用的測試指標有程式的執行時間、耗費的記憶體或 IO 的頻率等等,根據這些測試數據,就可以比較不同的程式之間的效能差異。
由於作業系統、軟硬體與機器當時的狀況都會影響程式的執行效能,另外機器上所安裝的 Perl 版本與其編譯時的選項(例如 iThreads 會讓 Perl 程式的整體效能提升)也都會有所影響,因此如果要比較不同程式之間的差異,只有在相同的執行環境下來測試才有意義。
Perl 的 Benchmark 是用來測量與比較程式執行時間的模組,以下是這個模組的安裝與使用方式。
安裝
Benchmark 是 Perl 的標準模組之一,您的系統如果有安裝 Perl 的環境,通常就可以直接使用了。您可以使用這行指令看看您的系統是否有安裝 Benchmark:
perl -e 'use Benchmark'
如果沒有出現任何錯誤訊息,那就表示您的系統已經安裝好 Benchmark 模組了,而如果沒有安裝 Benchmark 的話,就會出現類似這樣的訊息:
Can’t locate Benchmark.pm in @INC (you may need to install the Benchmark module) (@INC contains: /etc/perl /usr/local/lib/perl/5.18.2 /usr/local/share/perl/5.18.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.18 /usr/share/perl/5.18 /usr/local/lib/site_perl .) at -e line 1.
BEGIN failed--compilation aborted at -e line 1.
如果在 Ubuntu Linux 下,可以使用 apt 安裝:
sudo apt-get install libbenchmark-timer-perl
基本測試
如果只是單純要測試程式的執行時間,可以使用 new
這個方法(method):
#!/usr/bin/perl use Benchmark; # 測試開始 $t0 = Benchmark->new; # 要測試的程式碼 $c++ for 1 .. 10000000; # 測試結束 $t1 = Benchmark->new; $td = timediff($t1, $t0); print "The code took:", timestr($td), "\n";
輸出為
The code took: 1 wallclock secs ( 1.20 usr + 0.07 sys = 1.27 CPU)
另外也可以使用 timeit()
來進行重複測試,再計算平均的執行時間,這樣可以減少系統狀態不同所造成的誤差:
#!/usr/bin/perl use Benchmark; # 重複測試次數 $count = 10; # 重複測試 $count 次指定的程式碼 $t = timeit($count, sub { $c++ for 1 .. 10000000; }); print "$count loops of the code took:", timestr($t), "\n";
輸出為
10 loops of the code took: 6 wallclock secs ( 6.12 usr + 0.41 sys = 6.53 CPU) @ 1.53/s (n=10)
這個輸出中除了有執行時間的資訊之外,還有平均每秒執行的次數與執行的總次數。
另外有一個 timethis()
函數,它的功能跟 timeit()
類似,不過它會將測試結果自動輸出至 STDOUT
:
#!/usr/bin/perl use Benchmark; $count = 10; # 測試並輸出結果至 STDOUT $t = timethis($count, sub { $c++ for 1 .. 10000000; });
輸出為
timethis 10: 9 wallclock secs ( 8.58 usr + 0.64 sys = 9.22 CPU) @ 1.08/s (n=10)
timethis()
的第一個 $count
參數如果指定為負數,則代表最短的 CPU 執行時間,例如:
#!/usr/bin/perl use Benchmark; # 至少讓 CPU 執行 8 秒 $count = -8; $t = timethis($count, sub { $c++ for 1 .. 10000000; });
輸出為
timethis for 8: 9 wallclock secs ( 8.08 usr + 0.58 sys = 8.66 CPU) @ 1.27/s (n=11)
如果 $count
參數指定為 0
,則會以預設的 3 秒來執行(等同於 -3
)。
比較不同的程式
標竿測試除了測量單一個程式的執行時間之外,最常見的狀況就是比較不同程式之間的效能差異,Benchmark 模組中也提供了一個 timethese()
函數,可以同時測量與比較多個程式的執行時間:
#!/usr/bin/perl use Benchmark; # 同時測量與比較兩個程式,至少執行 5 秒 timethese(-5, { shiftAssign => sub { # 第一個程式 my @alphabet = ('A'..'Z'); for (my $i = 0; $i < 26; $i++){ my $letter = shift @alphabet; } }, equalsAssign => sub { # 第二個程式 my @alphabet = ('A'..'Z'); for (my $i = 0; $i < 26; $i++){ my $letter = $alphabet[$i]; } } } );
輸出為
Benchmark: running equalsAssign, shiftAssign for at least 5 CPU seconds…
equalsAssign: 5 wallclock secs ( 4.71 usr + 0.36 sys = 5.07 CPU) @ 60088.56/s (n=304649)
shiftAssign: 6 wallclock secs ( 5.43 usr + 0.40 sys = 5.83 CPU) @ 50626.42/s (n=295152)
這裡我們可以直接從平均每秒的執行次數看出來,equalsAssign 的效能比較好。(由於兩個程式實際執行的總時間是不一樣的,所以這裡無法直接從兩個程式的總執行次數來比較)
另一個 cmpthese()
函數的使用方式跟 timethese()
相同,不過它會輸出一個執行時間的比較表,方便看出不同程式之間的差異:
#!/usr/bin/perl use Benchmark qw/cmpthese/; # 比較兩個程式,輸出比較表 cmpthese(-5, { shiftAssign => sub { # 第一個程式 my @alphabet = ('A'..'Z'); for (my $i = 0; $i < 26; $i++){ my $letter = shift @alphabet; } }, equalsAssign => sub { # 第二個程式 my @alphabet = ('A'..'Z'); for (my $i = 0; $i < 26; $i++){ my $letter = $alphabet[$i]; } } } );
輸出為
Rate shiftAssign equalsAssign
shiftAssign 56443/s -- -25%
equalsAssign 75410/s 34% --
這個輸出的比較表中會依照執行的能從最慢的排到最快的,這裡顯示 equalsAssign 比起 shiftAssign 大約快了 34% 左右。
cmpthese()
也可以直接傳入 timethese()
的結果來產生比較表:
$results = timethese( ... ); cmpthese( $results );
注意事項
以下是一些測試時的一些小技巧:
- 盡量讓測試的程式單純化,盡可能將不重要的部分排除,這樣可以讓測試的數據更精準反映出程式的執行效能。
- 使用
timethese()
或cmpthese()
配合負值的$count
來指定最短的 CPU 執行時間,這樣可以減少誤差,一般建議使用 5 秒以上的測試時間可以得到比準確的結果。 - 實際測試之前,請確認您的測試程式碼是正確的,如果不確定是否有正確使用 Benchmark 模組,可以先用兩小段效能差很多的測試程式碼下去跑,確定 Benchmark 所得到的結果跟您預期的一樣。
- 在測試時,重點應該放在判斷執行效率最好的程式碼是哪一些,而不是單單只看執行的總時間,這樣才比較容易獲得一個最佳化的程式。
參考資料:Perl Tricks、dagolden、perldoc
繼續閱讀:Perl 程式設計教學