japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年11月22日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2008年8月12日 10:00

コードの最適化に役立つPHPスクリプトのベンチマーク

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!
  • この記事をokyuuへインポート

はじめに

 どのようなソフトウェアも、市場に出て洗練された製品になるまでには、最適化のプロセスを経る必要があります。メモリリークを見つけて製品のパフォーマンスを向上させるのは、多くの作業時間と人的資源を必要とする難しい作業です。最適化という課題においてベンチマークは重要です。個別のコード部品と全体のコードの両方を検証できますし、ベンチマークのレポートや統計データから、実際の実行時のパラメータやパフォーマンスを推測できるからです。

 PHPではBenchmarkパッケージを使用できます。これはPHPスクリプトや実行する関数のベンチマークに使われるPEARパッケージです。リリースされている最新版は1.2.7(安定版)です。パッケージのダウンロード後、次のようにしてインストールすることができます。

>> pear install Benchmark-1.2.7
 Benchmarkパッケージで何ができるかを示すため、フィボナッチ数列の生成という古典的な問題を例に、2つの解法(反復と再帰)を紹介します。この問題を詳しく説明すると本稿の目的から外れてしまいますが、問題自体は非常に一般的なものです。フィボナッチ数列の生成についてよくわからない場合や、もう一度フィボナッチ数列をよく理解したい場合は、こちらを参照してください。

PEAR Benchmarkのクラスツリー

 このパッケージには、ベンチマーク測定に使用できるいくつかのクラスが含まれています。

  • Benchmark_Timer
  • Benchmark_Iterate
  • Benchmark_Profiler
 本稿ではこの各クラスについて取り上げ、よく使われるメソッドと、具体的な使用例を紹介します。

Benchmark_Timerクラス

 Benchmark_Timerクラスには、正確なタイミング情報を返すメソッド一式が含まれています。このクラスで最もよく使われるメソッドのプロトタイプを次に示します。

  • void start() ― 開始マーカーを設定します。
  • void stop() ― 停止マーカーを設定します。
  • void setMarker(string $name) ― マーカーを設定します。$nameパラメータは、設定するマーカーの名前を示しています。
  • void display([boolean $showTotal = FALSE], [string $format = 'auto']) ― フォーマットされた測定結果を返します。$showTotalパラメータをtrueに設定すると、詳しい測定結果が出力されます。$formatパラメータには、出力フォーマットを指定します。デフォルトはautoで、他にはplain、またはhtmlがあります。autoが指定された場合は、PEARによってplainまたはhtmlが選択されます(ほとんどの場合はplainが選択されます)。
  • array getProfiling() ― 測定結果を連想配列で返します。$profiling[x]['name']は、マーカーxの名前を示します。$profiling[x]['time']は、マーカーxのタイムスタンプを示します。$profiling[x]['diff']は、マーカーx-1からマーカーxまでの実行時間を示します。また、$profiling[x]['total']は、マーカーxまでの実行時間の合計です。

フィボナッチ数列のベンチマーク(反復の場合)

 この例では、フィボナッチ数列を反復で求める処理にBenchmark_Timerクラスを適用し、フォーマットされた測定結果を返します。

<?php
   
require_once 'Benchmark/Timer.php';
   
function fibonacci(){
   //create an instance of Benchmark_Timer class
   $timer = new Benchmark_Timer();
         
   //Set "Start" marker
   $timer->start();
         
   $a=0;$b=1;
         
   for ($i = 0; $i < 10; $i++) {
      $s=$a+$b;
      
      //Set the markers fibonacci
      $timer->setMarker('fibonacci'.$i);
             
      $a=$b;
      $b=$s;
   } 
         
   //Set "Stop" marker
   $timer->stop();
         
   //Returns formatted informations
   $timer->display(); 
         
   echo '<pre>';
         
   //Get the profiler info as an associative array
   $profiling = $timer->getProfiling(); 
         
   //Display all the information: name, 
   // time, difference between two  
   //consecutive markers and total time
   print_r($profiling[1]);
   print_r($profiling[2]);
   print_r($profiling[3]);
   echo '</pre>';
}
   
fibonacci();
  
?>
 このコードでは、開始マーカーを設定した後で、フィボナッチ数列の最初の数値から10個を生成するループを開始します。値を1つ生成するたびに、「fibonacci+ループカウンタ値」という名前のマーカー(fibonacci1、fibonacci2など)を設定します。

 リスト1も同様のコードですが、こちらはフィボナッチ数列を再帰で求める処理についてのベンチマークテストです。

リスト1 フィボナッチ数列のベンチマーク(再帰の場合)(Fibonacci_recursive_timer.php)
<?php

require_once 'Benchmark/Timer.php';

//create an instance of the Benchmark_Timer class 
$timer = new Benchmark_Timer();

//Set "Start" marker
$timer->start();

function fibonacci($n){
       if(($n>=0)and($n<2))
                {return 1;}
                  else 
                   {return fibonacci($n-1)+fibonacci($n-2);}
}

for ($i = 0; $i < 10; $i++) {
     //Set the markers fibonacci
     $timer->setMarker('fibonacci'.$i);
     fibonacci($i);    
}   

//Set "Stop" marker
$timer->stop();

//Returns formatted informations
$timer->display(); 

echo '<pre>';

//Get the profiler info as an associative array
$profiling = $timer->getProfiling(); 

//Display all the information: name, time, 
//difference between two consecutives markers and 
//total time
print_r($profiling[1]);
print_r($profiling[2]);
print_r($profiling[3]);
echo '</pre>';

?>

ベンチマーク結果の出力

 この反復と再帰によるフィボナッチ数列プログラムは、どちらもフォーマットされたテーブルと連想配列という2つの形式で結果を出力します。

 テーブル形式の結果には、各マーカーの名前と、そのマーカーに到達するまでの経過時間(Time Index)、前のマーカーからそのマーカーに達するまでに要した時間(Ex Time)、さらには総時間に対するEx Timeの割合(%)が表示されます。

 反復の場合の結果は次のとおりです。

Time IndexEx TimePercentage
Start1209633208.32539500-0.00%
fibonacci01209633208.325465000.00007022.88%
fibonacci11209633208.325494000.0000299.48%
fibonacci21209633208.325516000.0000227.19%
fibonacci31209633208.325539000.0000237.52%
fibonacci41209633208.325562000.0000237.52%
fibonacci51209633208.325586000.0000247.84%
fibonacci61209633208.325608000.0000227.19%
fibonacci71209633208.325631000.0000237.52%
fibonacci81209633208.325656000.0000258.17%
fibonacci91209633208.325678000.0000227.19%
Stop1209633208.325701000.0000237.52%
Total-0.000306100.00%
 連想配列には、同じ情報が配列形式で格納されています。

Array
(
    [name] => fibonacci0
    [time] => 1209633208.32546500
    [diff] => 0.000070
    [total] => 0.000070
)
Array
(
    [name] => fibonacci1
    [time] => 1209633208.32549400
    [diff] => 0.000029
    [total] => 0.000099
)
Array
(
    [name] => fibonacci2
    [time] => 1209633208.32551600
    [diff] => 0.000022
    [total] => 0.000121
)
 再帰の場合の結果は次のとおりです。

Time IndexEx TimePercentage
Start1209633188.10306000-0.00%
fibonacci01209633188.103225000.0001657.83%
fibonacci11209633188.103305000.0000803.80%
fibonacci21209633188.103358000.0000532.52%
fibonacci31209633188.103430000.0000723.42%
fibonacci41209633188.103500000.0000703.32%
fibonacci51209633188.103592000.0000924.37%
fibonacci61209633188.103716000.0001245.89%
fibonacci71209633188.103883000.0001677.93%
fibonacci81209633188.104151000.00026812.72%
fibonacci91209633188.104553000.00040219.08%
Stop1209633188.105167000.00061429.14%
Total-0.002107100.00%
 同様に、連想配列には同じ情報が配列形式で格納されています。

Array
(
    [name] => fibonacci0
    [time] => 1209633188.10322500
    [diff] => 0.000165
    [total] => 0.000165
)
Array
(
    [name] => fibonacci1
    [time] => 1209633188.10330500
    [diff] => 0.000080
    [total] => 0.000245
)
Array
(
    [name] => fibonacci2
    [time] => 1209633188.10335800
    [diff] => 0.000053
    [total] => 0.000298
)
 ご覧のとおり、Benchmark_Timerクラスを使うと、マーカーを使って厳密な結果を得ることができます。しかし、ほとんどの場合は、個々のコード行よりも、まとまった関数のベンチマークを測定したいと思うのではないでしょうか。特に、関数自体のコードを修正せずにベンチマークを測定できれば便利です。そのためにはBenchmark_Iterateクラスを使用します。

Benchmark_Iterateクラス

 Benchmark_Iterateクラスには、関数のベンチマークに使用できるメソッドが2つあります。

  • void run() ― 関数のベンチマークを行います。
  • array get([ $simple_output = false]) ― ベンチマーク結果を配列として返します。結果配列で、$result[x]は反復xの実行時間を示します。$result['iterations']は反復回数、また$result['mean']は平均実行時間を示します。
 複雑な関数のベンチマークを行う前に、まず簡単な例を見てみましょう。ベンチマークのプロセスがよくわかるはずです。この例では、関数を定義して、その関数をrunメソッドで4回呼び出します。最後に結果を出力します。

<?php
require_once 'Benchmark/Iterate.php';
   
//create an instance of Benchmark_Iterate class
$benchmark = new Benchmark_Iterate;
   
function example($string) {
   print $string . '<br>';
}
   
//Benchmarks the example function
$benchmark->run(4, 'example', 'Octavia');
   
//Returns benchmark result
$result = $benchmark->get();
   
echo 'The number of iterations is '.$result['iterations'].'<br />';
echo 'The mean is: '.$result['mean'];
?> 
 このアプリケーションを実行すると、結果は次のようになります。

Octavia
Octavia
Octavia
Octavia
The number of iterations is 4
The mean is: 0.000064
 この簡単な例を理解したところで、今度は少し複雑な例を見てみましょう。リスト2では、フィボナッチ数列を反復で求める処理にBenchmark_Iterateクラスを適用しています。一方、リスト3では、フィボナッチ数列を再帰で求める処理にBenchmark_Iterateクラスを適用しています。各プログラムの実行結果は次のとおりです。

反復の結果
1 1 2 3 5 8 13 21 34 55 89 
The execution time of 1 iteration: 0.000223
The number of iterations is: 1
The mean is: 0.000223
再帰の結果
1 1 2 3 5 8 13 21 34 55 89 
The execution time of 1 iteration is: 0.001135
The number of iterations is: 1
The mean is: 0.001135
リスト2 関数レベルのベンチマーク(反復の場合)(fibonacci_iterative.php)
<?php

require_once 'Benchmark/Iterate.php';

//create an instance of the Benchmark_Iterate class 
$benchmark = new Benchmark_Iterate;

//this function implements the iterative solution 
//for the Fibonacci problem

function fibonacci(){
           $a=0;$b=1;
           echo $b.'   ';
           for ($i = 0; $i < 10; $i++) 
                {
                $s=$a+$b;
                echo $s.'   ';
                $a=$b;
                $b=$s;
               } 
           echo '<br />';
}

//Benchmarks the Fibonacci function
$benchmark->run(1, 'fibonacci');

//Returns benchmark result
$result = $benchmark->get();

//Returns execution time of iteration 1 using the $result variabile
echo 'The execution time of 1 iteration is: '.$result[1].'<br />';

//Returns the number of iterations using the $result variabile
echo 'The number of iterations is: '.$result['iterations'].'<br />';

//Returns the mean execution time
echo 'The mean is: '.$result['mean'];
?>
リスト3 関数レベルのベンチマーク(再帰の場合)(fibonacci_recursive.php)
<?php
require_once 'Benchmark/Iterate.php';

//create an instance of the Benchmark_Iterate class 
$benchmark = new Benchmark_Iterate;

echo 'The first 10 terms of the Fibonacci series are:   ';

//this function implements the recursive solution 
//for the fibonacci problem
function fibonacci($n){
   if(($n>=0)and($n<2))
     {return 1;}
   else 
     {return fibonacci($n-1)+fibonacci($n-2);}
}
for ($i = 0; $i <= 10; $i++) {
   echo fibonacci($i).'   ';
}

//Benchmarks the Fibonacci function
$benchmark->run(2, 'fibonacci');

//Returns benchmark result
$result = $benchmark->get();

echo 'The execution time of 1 iteration is: '.$result[1].'<br />';
echo '<br />'.'The number of iterations is: '
     .$result['iterations'].'<br />';
echo 'The mean is: '.$result['mean'];
?>

Benchmark_Profilerクラス

 Benchmark_Profilerクラスには、フォーマットされた測定結果を返すメソッド一式があります。このクラスで最もよく使われるメソッドのプロトタイプを次に示します。

  • void enterSection(string $name) ― コードセクションを開始します。$nameパラメータは、コードセクションの名前を示します。
  • void leaveSection(string $name) ― コードセクションを終了します。$nameパラメータは、コードセクションの名前を示します。
  • void display([string $format = 'auto']) ― フォーマットされた実行結果を返します。$formatパラメータは出力するフォーマットを表し、auto(デフォルト)、plain、またはhtmlを指定します。
 フィボナッチ数列を反復または再帰で求める処理にBenchmark_Profilerクラスを適用するコードを紹介します。

反復の場合
<?php
require_once 'Benchmark/Profiler.php';
   
//create an instance of Benchmark_Iterate class
$profiler = new Benchmark_Profiler(TRUE);
   
function fibonacci(){
   global $profiler;
   $a=0;$b=1;
   for ($i = 0; $i < 10; $i++) {    
       //Enters code section
       $profiler->enterSection('fibonacci'.$i);
      
       $s=$a+$b;
       $a=$b;
       $b=$s;
      
       //Leaves code section
       $profiler->leaveSection('fibonacci'.$i);      
   } 
   
   //Returns formatted profiling information
   $profiler->display();
   return;
}
fibonacci();
?>
再帰の場合
<?php
require_once 'Benchmark/Profiler.php';
   
//create an instance of Benchmark_Iterate class
$profiler = new Benchmark_Profiler(TRUE);
   
//this function implements the recursive solution 
//for the fibonacci problem
function fibonacci($n){
   
   if(($n>=0)and($n<2))
   {
      return 1;
   }
   else 
   {
      return fibonacci($n-1)+fibonacci($n-2);
   }
}
   
global $profiler;

for ($i = 0; $i < 10; $i++) {
   
   //Enters code section
   $profiler->enterSection('fibonacci'.$i);
   
   fibonacci($i).'<br />';
   
   //Leaves code section
   $profiler->leaveSection('fibonacci'.$i);
}
   
//Returns formatted profiling information
$profiler->display();
return;
?>
 それぞれの結果は次のとおりです。

反復の結果
Total Ex. TimeCallsPercentageCallers
fibonacci00.000252008438110351N/AGlobal (1)
fibonacci14.6968460083008E-0051N/AGlobal (1)
fibonacci23.814697265625E-0051N/AGlobal (1)
fibonacci35.1021575927734E-0051N/AGlobal (1)
fibonacci45.2928924560547E-0051N/AGlobal (1)
fibonacci55.6028366088867E-0051N/AGlobal (1)
fibonacci63.7908554077148E-0051N/AGlobal (1)
fibonacci74.0054321289063E-0051N/AGlobal (1)
fibonacci83.6954879760742E-0051N/AGlobal (1)
fibonacci90.000293016433715821N/AGlobal (1)
再帰の結果
Total Ex. TimeCallsPercentageCallers
fibonacci00.00013685226440431N/AGlobal (1)
fibonacci10.000316143035888671N/AGlobal (1)
fibonacci28.4877014160156E-0051N/AGlobal (1)
fibonacci37.9870223999023E-0051N/AGlobal (1)
fibonacci40.000118970870971681N/AGlobal (1)
fibonacci50.00012278556823731N/AGlobal (1)
fibonacci60.000164985656738281N/AGlobal (1)
fibonacci70.000385999679565431N/AGlobal (1)
fibonacci80.000369071960449221N/AGlobal (1)
fibonacci90.000570058822631841N/AGlobal (1)

ベンチマーク結果をグラフで取得する

 ベンチマーク結果の生の数値も役に立ちますが、棒グラフや円グラフのような形式で目にした方が役に立つ場合が少なくありません。fibonacci_iterative_timer.phpアプリケーションを拡張すると、Benchmark_Timerクラスを使ったベンチマークスクリプトにグラフ機能を追加できます。ここではSVGを使用して、ループの各反復に要した時間を視覚的に示す棒グラフと円グラフの画像を取得します。そのためには、測定結果の最後の列をグラフに描き、「ex time」列を視覚的に表現します。

<?php
   
require_once 'Benchmark/Timer.php';
   
function fibonacci(){
   //create an instance of Benchmark_Timer class
   $timer = new Benchmark_Timer();
         
   //Set "Start" marker
   $timer->start();
         
   $a=0;$b=1;
         
   for ($i = 0; $i < 15; $i++) {
      $s=$a+$b;
         
      //Set the markers
      $timer->setMarker('f'.$i);
         
      $a=$b;
      $b=$s;
   } 
         
   //Set "Stop" marker
   $timer->stop();
         
   //Returns formatted informations
   echo '<table>';
   echo '<tr>';
   echo '<td>';
          
   $timer->display();          
         
   echo '</td>';
   echo '<td>';
   //Get the profiler info as an associative array
   $profiling = $timer->getProfiling();    
          
   //serialize the $profiling
   $ser = serialize($profiling);
      
   // Display all the information: name, time, 
   // difference between two consecutive
   // markers, and total time
   echo "<embed src='DiagramSvg.php?profile=".$ser."' width='900'  
      height='500' type='image/svg+xml'  
      pluginspage='http://www.adobe.com/svg/viewer/install/' />";
   echo '</td>';
   echo '</tr>';
   echo '</table>';
}
   
//start the process
fibonacci(); 
     
?>
 このコードは、生の測定結果を示すテーブルを含んだHTMLページを出力します。このデータからSVG形式のグラフを2つ生成し(リスト4を参照)、テーブルの右側の列にAdobe SVGプレーヤを使って表示します。

 図1に、測定結果のテーブルとグラフを示します。Fibonacciメソッドの15回の反復結果を視覚的に示すことができます。

リスト4 ベンチマーク結果の生データから円グラフと棒グラフを生成するコード(DiagramSvg.php)
<?php

//include Graph.php and Canvas.php
require_once 'Image/Graph.php';    
require_once 'Image/Canvas.php'; 

$getser = $_GET['profile'];
 
$results = array();
$results = unserialize($getser);
 

//create a canvas
$Canvas = & Image_Canvas::factory(
   'svg', array('width' => 800, 'height' => 440));

//create the graph
$Graph =& Image_Graph::factory('graph', $Canvas);

//set Helvetica font
$Font =& $Graph->addNew('font', 'Helvetica');
//font size 10 pixels
 $Font->setSize(10);

//set the font to the graph
$Graph->setFont($Font);

//create the plotarea
$Graph->add(
   Image_Graph::vertical(
   Image_Graph::vertical(
   $Title = Image_Graph::factory(
      'title', array('Timer (the last column from the table) ', 15)),
   $SubTitle = Image_Graph::factory(
      'title', array('Bar and pie representation', 11)), 100),
   $Plotarea = Image_Graph::factory('plotarea'), 8));   

//set the title and subtitle at the right of the graphic        
$Title->setAlignment(IMAGE_GRAPH_ALIGN_RIGHT);
$SubTitle->setAlignment(IMAGE_GRAPH_ALIGN_RIGHT);

//add a bar grid   
$Grid =& $Plotarea->addNew('bar_grid', IMAGE_GRAPH_AXIS_X);

//add a gradient background
$Grid->setFillStyle(Image_Graph::factory('gradient', 
array(IMAGE_GRAPH_GRAD_RADIAL, 'white', 'lightgrey')));   

//add a bar graph
$dataset = & Image_Graph::factory('dataset');
for($i=0;$i<sizeof($results);$i++)
{
   //computing the graphic's values   
   $dataset->addPoint($i,(($results[$i][diff]*100.0) / 
      ($results[sizeof($results)-1][total])));
}
 
$Plot =& $Plotarea->addNew('Image_Graph_Plot_Bar', &$dataset);
$Plot->setLineColor('white');
$Plot->setFillColor('#cc0000');
  
$Marker =& Image_Graph::factory(
   'Image_Graph_Marker_Value', 
   IMAGE_GRAPH_VALUE_Y);
$Plot->setMarker($Marker);
$Marker->setFillColor('pink');
$Marker->setBorderColor('white');
  
$Plot =& $Plotarea->addNew('Image_Graph_Plot_Pie', &$dataset);
$Plot->setLineColor('pink');
$Plot->setFillColor('#cc0000');
  
$Marker =& Image_Graph::factory(
   'Image_Graph_Marker_Value', 
   IMAGE_GRAPH_VALUE_Y);
$Plot->setMarker($Marker);
$Marker->setFillColor('pink');
$Marker->setBorderColor('white');

//show axis    
$AxisY = $Plotarea->getAxis(IMAGE_GRAPH_AXIS_Y);
$AxisY->showArrow();
$Array = array();
for($i=0;$i<sizeof($results);$i++)
{
   $Array[$i] = $results[$i][name];
}
$AxisX = $Plotarea->getAxis(IMAGE_GRAPH_AXIS_X);
$AxisX -> setDataPreprocessor(
   Image_Graph::factory(
      'Image_Graph_DataPreprocessor_Array', 
      array($Array)));
  
//display the graph
$Graph->done();
?>       
図1 ベンチマーク結果のグラフ化。Fibonacciメソッドを15回反復した結果の生データと、その円グラフおよび棒グラフを重ねて表示した様子
図1 ベンチマーク結果のグラフ化。Fibonacciメソッドを15回反復した結果の生データと、その円グラフおよび棒グラフを重ねて表示した様子
 ここまで見てきたとおり、このPEARパッケージを使ってPHPスクリプトにベンチマーク処理を追加するのは簡単で単純な作業です。この方法は、新しくコードを書くときだけでなく、既存のPHPコードのベンチマークが必要なときにも簡単に適用できます。しかも、ベンチマークの結果はわかりやすく、処理も簡単です。こうなると、PHPアプリケーションの最適化をサボる口実はほとんどなくなります。今は必要のないステップだと思っているものでも、後で大きな違いとなって現れてくることでしょう。

著者紹介

Octavia Andreea Anghel(Octavia Andreea Anghel)
経験豊富なPHP開発者。現在は、国内外のソフトウェア開発コンテストに参加するプログラミングチームの主任トレーナーを務める。国レベルの教育プロジェクト開発のコンサルティングも担当している。共著書に『XML technologies?XML in Java』があり、XML部分の執筆を担当。PHPやXMLのほか、ソフトウェアアーキテクチャ、Webサービス、UML、ハイパフォーマンスな単体テストについても関心を寄せている。
関連テーマ
このエントリーを含むはてなブックマーク この記事をクリップ!
BuzzurlにブックマークBuzzurlにブックマーク Yahoo!ブックマークに登録
この記事をokyuuへインポート
最新トップニュース
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「時代とメディアの変化に素早く対応したサービスを提供し続ける!!」/ユニバース株式会社(11月21日)
データメーション
【データメーション】
Yahoo を応援する(11月20日)
Graphic Design Forum
【Graphic Design Forum】
もう決めたの? (11月17日)
エンジニアの独り言
【エンジニアの独り言】
不景気だからこそ(11月14日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/