バイトコードキャッシュによるパフォーマンスの改善

Propelは大きなクラスファイルを生成します。これらは重要な解析時間のパフォーマンスヒットを作ります。バイトコードキャッシュは解析時間のコストを大いに削減することが出来ます。

Propelを使用する半分まじめなプロジェクト(10テーブル以上)はパフォーマンス問題に基づく重大な解析時間に苦しみます。これはしかしながらPropelのORMの方法では特有でSmartyのような大きなクラスを使うことで問題が追加されます。

不幸にもバイトコードキャッシュ全体の問題はPHPではよく取り扱われていません:

1. Zend社の業務用Acceleratorは今や高額なプラットフォームに埋もれています。

2. eAcceleratorは今現在でおそらくベストな選択肢です、しかし完全にPHP5.1.xに対して互換性がありません。0.9.5で対処されつつありますが(そしてとても安定しているように見えます - テストしてみて下さい!)。

3. APC もPHP5.1.xのバグに苦しめられています。しかしながらラスマス(Rasumus)にってサポートされPHP6のコアに含めるかどうか議論されています。

バイトコードキャッシュによって、クラスの大きさはより小さな問題になります。次のテストセットアップはバイトコードキャッシュの恩恵の感覚を得るために使うことが出来ます。

環境: 単体のAMD Athlon 64 3200+ 2.0 GHz, FreeBSD 5.3, PHP 5.0.4, eAccelerator 0.93

test script

<?php
/**
 * Propelのためのパフォーマンスプロファイリング
 * クラスのための解析時間に焦点を当てる
 */

require_once 'lib/Timer.php';
// Propelは共有された1.1.1クラスではなく1.2のブランチクラスが含まれます
ini_set('include_path', '.:/usr/home/oliver/propelsvn/packages');

// 我々が要求するこれらのクラスの順番は重要です
// それぞれの独自の命令文にもかかわらず個々の時間を測定できるからです
$requires = array(
  // オーバーヘッドクラスの開始
  'creole/ResultSet.php',
  'creole/Connection.php',
  'creole/PreparedStatement.php',
  'creole/common/ConnectionCommon.php',
  'creole/common/PreparedStatementCommon.php',
  'creole/CreoleTypes.php',
  'creole/common/ResultSetCommon.php',
  'creole/drivers/mysql/MySQLResultSet.php',
  'creole/drivers/mysql/MySQLConnection.php',
  'creole/drivers/mysql/MySQLPreparedStatement.php',
  'creole/SQLException.php',
  'creole/Creole.php',
  'propel/adapter/DBAdapter.php',
  'propel/adapter/DBMySQL.php',
  'propel/PropelException.php',
  'propel/Propel.php',
  'propel/om/Persistent.php',
  'propel/om/BaseObject.php',
  'propel/util/Criteria.php',
  'propel/map/ValidatorMap.php',
  'propel/map/ColumnMap.php',
  'propel/map/TableMap.php',
  'propel/map/DatabaseMap.php',
  'propel/map/MapBuilder.php',
  'propel/validator/ValidationFailed.php',
  'propel/util/BasePeer.php',
  // オーバーヘッドクラスの終わり

  // 生成されたクラスの始まり
  // lib/propel/CustomerOrder.phpは
  // 'BaseCustomerOrderPeer.php'をコメントアウトすることを要求したので
  // それの解析を個別に測定することが出来る
  'lib/propel/CustomerOrder.php',
  'lib/propel/map/CustomerOrderMapBuilder.php',
  'lib/propel/CustomerOrderPeer.php'
  // 生成されたクラスの終わり
  );

$master_timer = new Timer('Master Timer');
$master_timer->start();

foreach ($requires as $require)
{
  require_once $require;
  $master_timer->lap($require);
}

// confファイルの解析を含む
// がこれは最小にすべきである
Propel::init('conf/order-conf.php');
$master_timer->lap('Propel::init()');

// 接続を得るためにどのくらい時間がかかるのか
// 調べることも興味深い
$con = Propel::getConnection();
$master_timer->lap('Propel::getConnection()');

// 何か役立つことをさせるふりをします
$c = new Criteria();
$c->setLimit(30);
$orders = CustomerOrderPeer::doSelect($c);
$master_timer->lap('CustomerOrderPeer::doSelect($c)');

// そしてレポートを作成します
?>
<!DOCTYPE html 
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
  <title>propel peformance - parse time</title>
  <meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1' />
</head>
<body>
<pre>
<?php
echo 'number of orders retrieved: ' . count($orders) . "\n";
foreach ($orders as $order)
{
  echo $order->getDatePurchased() . "\n";
}
$master_timer->lap('foreach echo $order->getDatePurchased()');
  
$master_timer->stop();
echo $master_timer->report();

?>
</pre>
</body>
</html>

簡潔にするためにTimerクラスはここでは含まれませんが、何が行われるのか推測出来るでしょう。

これによってeAcceleratorが無効の状態で次の結果が出力されます。時間はミリ秒(ms)単位です。ページは最小の実行が行われるまでリフレッシュされました。この時間は負荷がかかっていないサーバ上でとても一貫しています。

----------------------------------------------------------------------
Label                                               Split      Lap    
----------------------------------------------------------------------
start1                                               0.028     0.028  
creole/ResultSet.php                                 0.930     0.902  
creole/Connection.php                                1.599     0.669  
creole/PreparedStatement.php                         2.193     0.594  
creole/common/ConnectionCommon.php                   2.843     0.650  
creole/common/PreparedStatementCommon.php            4.810     1.967  
creole/CreoleTypes.php                               5.457     0.647  
creole/common/ResultSetCommon.php                    7.068     1.611  
creole/drivers/mysql/MySQLResultSet.php              8.492     1.424  
creole/drivers/mysql/MySQLConnection.php            10.196     1.704  
creole/drivers/mysql/MySQLPreparedStatement.php     10.918     0.722  
creole/SQLException.php                             11.343     0.425  
creole/Creole.php                                   12.895     1.552  
propel/adapter/DBAdapter.php                        13.619     0.724  
propel/adapter/DBMySQL.php                          14.251     0.632  
propel/PropelException.php                          14.604     0.353  
propel/Propel.php                                   16.547     1.943  
propel/om/Persistent.php                            16.941     0.394  
propel/om/BaseObject.php                            17.689     0.748  
propel/util/Criteria.php                            22.208     4.519  
propel/map/ValidatorMap.php                         22.722     0.514  
propel/map/ColumnMap.php                            23.689     0.967  
propel/map/TableMap.php                             25.285     1.596  
propel/map/DatabaseMap.php                          25.910     0.625  
propel/map/MapBuilder.php                           26.243     0.333  
propel/validator/ValidationFailed.php               26.663     0.420  
propel/util/BasePeer.php                            31.610     4.947  
lib/propel/CustomerOrder.php                        38.457     6.847  
lib/propel/map/CustomerOrderMapBuilder.php          39.568     1.111  
lib/propel/CustomerOrderPeer.php                    43.522     3.954  
Propel::init()                                      45.020     1.498  
Propel::getConnection()                             45.985     0.965  
CustomerOrderPeer::doSelect($c)                     56.712    10.727  
foreach echo $order->getDatePurchased()             57.138     0.426  
stop34                                              57.152     0.014  

eAcceleratorが有効になっている場合です:

----------------------------------------------------------------------
Label                                               Split      Lap    
----------------------------------------------------------------------
start1                                               0.027     0.027  
creole/ResultSet.php                                 0.303     0.276  
creole/Connection.php                                0.702     0.399  
creole/PreparedStatement.php                         0.933     0.231  
creole/common/ConnectionCommon.php                   1.174     0.241  
creole/common/PreparedStatementCommon.php            1.450     0.276  
creole/CreoleTypes.php                               1.695     0.245  
creole/common/ResultSetCommon.php                    1.955     0.260  
creole/drivers/mysql/MySQLResultSet.php              2.584     0.629  
creole/drivers/mysql/MySQLConnection.php             3.390     0.806  
creole/drivers/mysql/MySQLPreparedStatement.php      4.000     0.610  
creole/SQLException.php                              4.223     0.223  
creole/Creole.php                                    4.782     0.559  
propel/adapter/DBAdapter.php                         5.183     0.401  
propel/adapter/DBMySQL.php                           5.603     0.420  
propel/PropelException.php                           5.820     0.217  
propel/Propel.php                                    6.401     0.581  
propel/om/Persistent.php                             6.629     0.228  
propel/om/BaseObject.php                             7.027     0.398  
propel/util/Criteria.php                             7.486     0.459  
propel/map/ValidatorMap.php                          7.723     0.237  
propel/map/ColumnMap.php                             8.168     0.445  
propel/map/TableMap.php                              8.770     0.602  
propel/map/DatabaseMap.php                           9.163     0.393  
propel/map/MapBuilder.php                            9.387     0.224  
propel/validator/ValidationFailed.php                9.615     0.228  
propel/util/BasePeer.php                            11.069     1.454  
lib/propel/CustomerOrder.php                        12.213     1.144  
lib/propel/map/CustomerOrderMapBuilder.php          12.689     0.476  
lib/propel/CustomerOrderPeer.php                    13.556     0.867  
Propel::init()                                      14.873     1.317  
Propel::getConnection()                             15.829     0.956  
CustomerOrderPeer::doSelect($c)                     26.071    10.242  
foreach echo $order->getDatePurchased()             26.481     0.410  
stop34                                              26.494     0.013  

手短な分析

アクセレーション無しではPropelのオーバーヘッドクラスは31msの処理を行います。加えてそれぞれのテーブルごとに~12msです(フィールドと外部キーの数によります)。10テーブルを持つ適切なプロジェクトのために、これは解析時間のオーバーヘッドを作ります。31 +(10 *12) ≒ 151msです。

eAcceleratorを有効にすることでPropelのオーバーヘッドは11msまで短縮されます。それぞれのテーブルは~2.5ms必要なので同じ10テーブルのプロジェクトのための見積もりは次のようになります: 11 + (10 * 2.5) = 36ms。相対的にも(4.2倍)絶対的な時間(一つのリクエストごとに115ms節約)においてもオーバーヘッド時間(もちろん全体の実行時間は変わります)のとても大きな削減です。

テストはPHP 5.0.4で行われました。APCとeAcceleratorは現在PHP 5.1.xでは信頼性が無いからです。

さらなる解析が続きます....