PerlよりPHPの方が軽くて速いは本当?

川合孝典(2002/3/12)

元々メーリングリストにも流してWikiで公開していたものなんですが、Wikiのサイトが停止したこともあり、こちらに移植して編集しました。バージョンが多少古い目のものが多くなっていますが、このベンチを行ったときには、それほど古くないバージョンだったはずなんですけどねぇ。

結論から先にいましょう。私としてはPHPは立ち上がりは速いけどPerlだって負けてないし、処理そのものはPerlのほうが速いよんということだと思っています。ついでに憎まれ口を叩くと「PHPが比較している相手はPerl4なのでは?」と思っています。「CGIとPerlはきちんと区別してね」とか。「JavaはPerlよりも比較にならないほど速い?」の計算問題のあたりも合わせてご覧ください。あわせてPerl/DBIの次のステップをご覧いただくと参考になるかもしれません。

私としては"○○はCGIよりも速い。だからPerlよりも速い"っていう無茶苦茶なことでもなければあまり気にしません。(今だに結構あるんですよね。Perl=CGIだと書いちゃうような人が。 Perl/CGIって書いて比較していたと思ったら、一番最後にはPerlって書いちゃうようなケースとか。 ex. http://www.atmarkit.co.jp/flinux/php4/php4_1/php1.html )

できればPHPがPerlよりも(もちろんCGIじゃなくてですよ)という根拠になるような資料、PHPが得意な人による比較というものも見たいので、どなたかご存知でしたら教えてください。


そもそものはじまり

PHPといえば日本PHPユーザー会(http://www.php.gr.jp)のトップページにもある通り、

通常のCGIとして使用できますが、PHPモジュールをApacheサーバーに組み込むことにより、 Perl/CGIと比較して処理速度の高速化、サーバー負荷の低減が可能です。

とPerlよりも速いというケースが多いようです。CGIってついてるけど、ことさらにPerlって書いてるぐらいだから、mod_perlよりも速いように思っていました。 こんなこと書くくらいだからぶっちぎりで速いんだろうなぁなんて思ってんです。本家dbi-usersでもちょいと 話題になったときでもPHPにはスピードで負けてもみたいな空気があったように感じていたので。

ところが、今回仕事の関係でPHPを調査する機会があってベンチマークをとってみたのですが、私としては手放しで PHPは速いとは思えませんでした。そりゃCGIのPerlと比較したら無条件に速いといえるでしょうけど、 mod_phpの相手は当然mod_perlでしょ?pconnectするんならApache::DBIを使うでしょ?

通常の接続であればPgを利用したプログラムとほぼ同等でしたし、持続的接続を使っている場合であっても レコード数が多ければ、適切にDBIを利用したスクリプトが追い抜いてしまいます。 Perl/DBIのほうが汎用的なインターフェースなのに。 (class.DBIやPEAR.DBはさらに遅かったので対象外ですね)

もちろんこういったベンチマークでは適切な環境で適切なスクリプトを使わなければ正しい値は期待できないと 思います。私の場合、当然(^^)、Perl側に有利な数字がでてしまうのはある程度当たり前だと思っています。 (思っていなかった人は思ってね)

この結果はあくまでも、参考ということで御自分で確認するようにお願いします。 川合がウソ書いているかもしれないと確かめてください。できれば、その結果も教えて欲しいと思っています。 自分のマシンだけじゃ、間違っているのかあっているのかもよくわからないので。<(__)>

特にPHP側については、PHP-usersメーリングリストでは質問させていただき、 指摘された部分は修正しましたが、まだ正しいかどうか不安なのです。


実行環境

今回のベンチマークは、すべて同じマシンを利用しています。

テスト方法

Apacheのベンチマーク abを利用し、以下のようなコマンドで
./ab -c10 -n100 http://lins/perl/tpg.pl  

それぞれ5回実行、Requests per secondを記録。 (そのようなシェルスクリプトを作成したんですが)

テスト・パターン

mod_perlの環境としてはApache::Registryを使用し、CGI.pm、DBI、DBD::Pg、Pgをロードしています。


テスト結果

これらのベンチマークについては何回も行ない平均値を取っています。また実行したタイミングによって出てくる数字は多少違ってきますが、傾向としては同じものと考えています。

通常接続

10レコードの場合

  bind arrayref array hashref Pg PHP
総平均 13.47 13.37 13.32 12.98 14.07 14.00
一回の時間(ms) 74.24 74.77 75.10 77.06 71.08 71.41

50レコードの場合

  bind arrayref array hashref Pg PHP
総平均 11.90 11.62 11.22 10.19 11.79 11.13
一回の時間(ms) 84.00 86.05 89.12 98.14 84.82 89.82

※ちなみにPerlをCGIで利用すると総平均で大体1.77程度。CGIは遅いよね。

持続的接続(Apache::DBI,pconnect)

Pgには持続的接続がないこともあってDBIのarrayrefを使って計測。

10レコードの場合

  arrayref PHP
総平均 38.62 45.96
一回の時間(ms) 25.90 21.76

50レコードの場合

  arrayref PHP
総平均 26.88 24.99
一回の時間(ms) 37.21 40.01

結果についてのまとめ


結果を踏まえて私が考えたこと

その他のメモ

オマケ:この結果を元にPerl側の設定を最速モード(^^)にしたところ
  -bind_columns+fetchrow_arrayrefを利用
  -CGI.pmの代わりにApacheモジュールを利用
  -startup.plで、Apache::DBI->connect_init+RegistryLoaderの利用
通常接続、持続的接続ともに15レコード程度でPHPとほぼ同じ速度になった。
(従来は30〜40レコード)

御参考

教えてもらったmod_perlのチューニングガイド 
Performance Tuning  mod_perl Database Performance Improving
   http://perl.apache.org/guide/performance.html

 


使用したスクリプト

Perl

*fetchrow_array+bind_columns

普通ならこれでPHPだろうがPgだろうが怖いものなし(^^?
#!/usr/bin/perl
 use strict;
 use CGI;
 use DBI;
 my $oCgi = new CGI;
 my $hDb = DBI->connect('dbi:Pg:host=lins dbname=test',
         'scott', 'tiger', {AutoCommit=>0, RaiseError=>1}) ||
       die "OPEN ERROR:" . $DBI::errstr;
 my $hSt = $hDb->prepare(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ? OFFSET 100');
 $hSt->execute($oCgi->param('limit')+0);
 my $raRow;
 my $sRes ='';
 my $i=0;
 $hSt->bind_columns(\(
 my ($no,
     $name01, $name02, $name03, $name04, $name05,
     $name06, $name07, $name08, $name09, $name10,
     $name11, $name12, $name13, $name14, $name15,
     $name16, $name17, $name18, $name19,)
 ));
 while($raRow = $hSt->fetchrow_arrayref()) {                      
     for(my $k=0; $k<20;$k++){ $sRes .= $raRow->[$k];}
     $sRes.='<BR>';
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();
 

*fetchrow_arrayref

普通ならこれでも十分でしょう
#!/usr/bin/perl
 use strict;
 use CGI;
 use DBI;
 my $oCgi = new CGI;
 my $hDb = DBI->connect('dbi:Pg:host=lins dbname=test',
         'scott', 'tiger', {AutoCommit=>0, RaiseError=>1}) ||
       die "OPEN ERROR:" . $DBI::errstr;
 my $hSt = $hDb->prepare(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ? OFFSET 100');
 $hSt->execute($oCgi->param('limit')+0);
 my $raRow;
 my $sRes ='';
 my $i=0;
 while($raRow = $hSt->fetchrow_arrayref()) {
     for(my $k=0; $k<20;$k++){ $sRes .= $raRow->[$k];}
     $sRes.='<BR>';
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();

Perl DBI fetchrow_array

#!/usr/bin/perl
 use strict;
 use CGI;
 use DBI;
 my $oCgi = new CGI;
 my $hDb = DBI->connect('dbi:Pg:host=lins dbname=test',
         'scott', 'tiger', {AutoCommit=>0, RaiseError=>1}) ||
       die "OPEN ERROR:" . $DBI::errstr;
 my $hSt = $hDb->prepare(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ? OFFSET 100');
 $hSt->execute($oCgi->param('limit')+0);
 my @raRow;
 my $sRes ='';
 my $i=0;
 while(@raRow = $hSt->fetchrow_array()) {
     for(my $k=0; $k<20;$k++){ $sRes .= $raRow[$k];}
     $sRes.='<BR>';
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();

Perl DBI fetchrow_hashref

カラム名が欲しければ$hSt->{name}などを利用しましょう。 そういえばPHPでは大文字小文字はどうなるんだろう?

#!/usr/bin/perl
 use strict;
 use CGI;
 use DBI;
 my $oCgi = new CGI;
 my $hDb = DBI->connect('dbi:Pg:host=lins dbname=test',
         'scott', 'tiger', {AutoCommit=>0, RaiseError=>1}) ||
       die "OPEN ERROR:" . $DBI::errstr;
 my $hSt = $hDb->prepare(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ? OFFSET 100');
 $hSt->execute($oCgi->param('limit')+0);
 my $raRow;
 my $sRes ='';
 my $i=0;
 while($raRow = $hSt->fetchrow_hashref()) {
     $sRes .= $raRow->{no};
     for(my $k=1; $k<20;$k++){
         $sRes .= $raRow->{sprintf("name%02d",$k)};
    }
    $sRes.='<BR>';
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();

Perl Pg

DBIとなるべく似せるようにはしたんですけど。
#!/usr/bin/perl
 use strict;
 use CGI;
 use Pg;
 my $oCgi = new CGI;
 my $oConn = Pg::connectdb(
     'host=lins dbname=test user=scott password=tiger');
 die $oConn->errorMessage if($oConn->status != PGRES_CONNECTION_OK);
 my $result = $oConn->exec(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ' .
 $oCgi->param('limit') . ' OFFSET 100');
 my @raRow;
 my $sRes ='';
 my $i=0;
 while(@raRow = $result->fetchrow()) {
     for(my $k=0; $k<20;$k++){ $sRes .= $raRow[$k];}
    $sRes.='<BR>'; 
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();

Prel DBI Apache + bind_columnsのフルチューン

といっても通常パターンとの違いを見つけるほうが面倒なくらいでしょうけど。
#!/usr/bin/perl
 use strict;
 use Apache;
 use DBI;
 my $hDb = DBI->connect('dbi:Pg:host=lins dbname=test',
         'scott', 'tiger', {AutoCommit=>0, RaiseError=>1}) ||
       die "OPEN ERROR:" . $DBI::errstr;
 my $hSt = $hDb->prepare(
 'SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT ? OFFSET 100');
 #my $oCgi = Apache->request();
 my %hArgs = Apache->request()->args();
 $hSt->execute($hArgs{limit}+0);
 my $raRow;
 my $sRes ='';
 my $i=0;
 $hSt->bind_columns(\(
 my ($no,
     $name01, $name02, $name03, $name04, $name05,
     $name06, $name07, $name08, $name09, $name10,
     $name11, $name12, $name13, $name14, $name15,
     $name16, $name17, $name18, $name19,)
 ));  
 while($raRow = $hSt->fetchrow_arrayref()) {
     #for(my $k=0; $k<20;$k++){ $sRes .= $aRow[$k];}
     $sRes .= $no.
     $name01. $name02. $name03. $name04. $name05.
     $name06. $name07. $name08. $name09. $name10.
     $name11. $name12. $name13. $name14. $name15.
     $name16. $name17. $name18. $name19. '<BR>';
     $i++;
 }
 print<<EOH;
 Content-Type: text/html

 <HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 $sRes
 </BODY>
 </HTML>
 EOH
 $hSt->finish();
 $hDb->disconnect();

PHP

これはpconnect版。通常接続では代わりにconnectを使用
<HTML>
 <HEAD>
 <TITLE>TEST</TITLE>
 </HEAD>
 <BODY>
 <?php
   $conn=pg_pConnect("host=lins dbname=test user=scott password=tiger");
   $result = pg_Exec($conn,
 "SELECT * FROM TBL_TEST2 WHERE no >= 5000 ORDER BY no LIMIT $limit OFFSET 100");
   $sRes = '';
   while(@$row = pg_Fetch_Row ($result, $i++)){
     for($k=0;$k<20;++$k) { $sRes .= $row[$k];}
     $sRes .= '<BR>';
   }
   echo $sRes;
 ?>
 </BODY>
 </HTML>
 <?php
   pg_Close($conn);
 ?>

 
河馬屋二千年堂