バイトコードキャッシュによるパフォーマンスの改善
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では信頼性が無いからです。
さらなる解析が続きます....
