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 Tricksdagoldenperldoc

繼續閱讀:Perl 程式設計教學