Symfony ORM のパフォーマンス比較 (1) symfony 1.4 & Doctrine 1.2
この記事は、Symfony アドベントカレンダー 2010 に参加しています。
以前、DoctrineとPropelのパフォーマンス比較 - しんふぉにゃんという記事を書きました。その記事が2009年9月なので、1年以上前ですね。それまでPropel派だった私はその記事以降、Doctrine が(パフォーマンス的にも)結構使えるということが分かり、すっかり Doctrine 使いになってしまっています。その後 Doctrine も Propel も、また symfony 本体もバージョンアップしていますので、比較結果に変化があるかもしれません。
また、現在最も気になるのは、Symfony 2 と Doctrine2 のパフォーマンスじゃないでしょうか。
このあたりを確かめるために、新しいバージョンで同じような処理を行って比較してみたいと思います。
環境
環境は以下のようになっています。前回よりパワーアップしています。
- CPU Core i7 920
- OS Ubuntu 10.10 (デスクトップ版 32bit)
- ノーマルなHDD(SSDではありません)
- メモリ 3GB
- PHP 5.3.3、APC有効
- MySQL 5.1.49
また、前回は触れていませんでしたが、MySQLのmy.cnfで以下の設定をしています。
- innodb_flush_log_at_trx_commit=0
(この設定には注意が必要ですが、insertのテストでは大きく性能に関わりますので0にしています)
symfony 1.4 & Doctrine 1.2
今回は、あまり面白みがないかもしれませんが、現役でバリバリ活躍しているこの構成からです。
symfony 1.4.8のソースパッケージをインストールし、frontendアプリケーションを作り、上記のスキーマをconfig/doctrine/schema.ymlに記述してDBとモデルを作成しました。
INSERT 1万回
まずはINSERTからです。前回の記事で、ORMで素直にやる方法と、Doctrineのコネクションオブジェクトを取得してinsertメソッドを使う方法です。
適当なタスクを作成し、タスクの処理本体部分に以下のように記述します。
【処理A】
<?php $startTime = microtime(true); for ($i = 0; $i < 10000; ++$i) { //echo $i . PHP_EOL; $address = new Address(); $address->setPrefName('岐阜県'); $address->setZip1('111'); $address->setZip2('2222'); $address->setAddr1('山奥の村一丁目一番地'); $address->save(); $address->free(); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
次に、コネクションオブジェクト(Doctrine_Connection)を取得して、コネクションオブジェクトのinsert()メソッドを使います。
【処理B】
<?php $startTime = microtime(true); $table = Doctrine::getTable('Address'); $conn = $table->getConnection(); for($i=0; $i<10000;++$i){ $conn->insert($table,array( 'zip1'=>'111', 'zip2'=>'2222', 'pref_name'=>'岐阜県', 'addr1'=>'山奥の村一丁目一番地', )); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
処理Aと処理Bのそれぞれの実行時間とメモリ使用量は以下のようになります。
主キーランダム SELECT 1万回
次に、ランダムな主キーで1件のレコードを取得する処理を実行してみます。
データベースは、テーブルのオートインクリメント値をクリアした後に、INSERT 1万回のテストでデータを投入した状態なので、IDが1から10000のレコードがあります。
Doctrineのクエリーキャッシュのあり/なしとクエリーの書き方の違い、それにハイドレーションあり/なしを組み合わせて比較してみます。
【処理A】クエリーキャッシュなし、ハイドレーションなし
<?php $startTime = microtime(true); $q = AddressTable::getInstance()->createQuery('a')->where('a.id = ?'); for ($i = 0; $i < 10000; ++$i) { $rec = $q->fetchOne(array((int)rand(1, 10000)), Doctrine_Core::HYDRATE_NONE); unset($rec); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
※ハイドレーションありは、Doctrine_Core::HYDRATE_NONEを削除
【処理B】クエリーキャッシュあり、ハイドレーションなし
<?php $startTime = microtime(true); $manager = Doctrine_Manager::getInstance(); $manager->setAttribute(Doctrine::ATTR_QUERY_CACHE, new Doctrine_Cache_Apc()); $q = AddressTable::getInstance()->createQuery('a')->where('a.id = ?'); for ($i = 0; $i < 10000; ++$i) { $rec = $q->fetchOne(array((int)rand(1, 10000)), Doctrine_Core::HYDRATE_NONE); unset($rec); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
【処理C】クエリーキャッシュなし、ハイドレーションなし、クエリーをループ内で生成
<?php $startTime = microtime(true); for ($i = 0; $i < 10000; ++$i) { $q = AddressTable::getInstance()->createQuery('a')->where('a.id = ?'); $rec = $q->fetchOne(array((int)rand(1, 10000)), Doctrine_Core::HYDRATE_NONE); $q->free(); unset($rec, $q); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
これらの3つの処理の、それぞれの実行時間とメモリ使用量は以下のようになります。
まとめ(にはあまりなっていませんが)
- Doctrine1は、ハイドレーションのコストは高い
- 今回のテストのような使い方(バッチの先頭のみでクエリーを作成している)では、クエリーキャッシュの恩恵はない
- ループ内で、同じクエリーオブジェクトを使い回せるため、再生成の必要がない
- クエリーオブジェクトの作成コストは無視できる程度?
- 大量のINSERTでは特に、コネクションオブジェクト経由でinsertしたときの速度改善が大きい
このまとめだけでは、なんだかよく分かりませんね・・・。(検証が正しいのかもちょっと自信がないので、みなさんもやってみてツッコミお願いします)
ただ、今回のテストのように1回の処理で扱うデータの件数がある程度大きくなる場合は、ハイドレーションのオーバーヘッドが顕著になりますね。
次回は、同じ検証を、Symfony2 & Doctrine2 でやったものを掲載予定です。
Symfony Advent 2010であなたの記事を公開してみませんか?
Symfony Advent 2010では12月1日から12月24日までを使って日替わりでsymfonyでイイなと思った小さなtipsから内部構造まで迫った解説などをブログ記事にし て公開していくイベントです。
参加についてはATNDで参加表明の上、Google
GroupのSymfony Advent 2010に追加リクエストを送信ください。
Symfony Advent 2010チーム一同、あなたの参加をお待ちしております。
日本Symfonyユーザー会
Symfony アドベントカレンダー2010
※Syfony Advent 2010はsymfony好きな有志で集まったチームです。