Symfony ORM のパフォーマンス比較 (2) Symfony2(PR4) & Doctrine2
この記事は、Symfony アドベントカレンダー 2010 に参加しています。
さて、今回はいきなりですが、Symfony2 と Doctrine2 を使って計測してみます。
この計測を行うには、以下のような準備が必要なので、それは日を改めて記事にしたいと思います。
- Symfony2/Doctrine2 環境でのエンティティクラスの準備
- Symfony2 でコマンドの作成
今回は、上記準備は整っているものとして、計測に使ったコード部分のみを掲載します。
環境
使用したバージョンは以下です。
- Symfony2 PR4
- Doctrine2 ORM BETA4
※Symfony2 Sandbox に付属している Doctrine2 ORM は BETA4 でやや古いですが、そのままで。
Symfony2 & Doctrine2 ORM
INSERT 1万回
Doctrine2 で INSERT を行うには、エンティティオブジェクトを作って EntityManager で persist する方法と、DBAL から Connection オブジェクトを取得して、Connection オブジェクトの insert() メソッドを使う方法があります。
【処理A】 - エンティティをpersist
<?php $startTime = microtime(true); $em = $this->container->get('doctrine.orm.entity_manager'); for ($i = 0; $i < 10000; ++$i) { $address = new Address(); $address->setPrefName('岐阜県'); $address->setZip1('111'); $address->setZip2('2222'); $address->setAddr1('山奥の村一丁目一番地'); $em->persist($address); } $em->flush(); echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
【処理B】 - DBAL の Connection オブジェクトで insert()
<?php $startTime = microtime(true); $conn = $this->container->get('doctrine.dbal.default_connection'); for ($i = 0; $i < 10000; ++$i) { $conn->insert('address', array( 'pref_name' => '岐阜県', 'zip1' => '111', 'zip2' => '2222', 'addr1' => '山奥の村一丁目一番地', )); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
これらの処理時間とメモリ使用量は次のようになりました。(参考までに、symfony 1.4/Doctrine 1.2の時の数値も並べてあります)
なんだか極端な結果がでましたね・・・。爆速ですが、メモリも激食いです・・・・。
ちなみに、コマンドの処理開始時点での memory_get_peak_usage() の結果は7.5MB程度です。Connection オブジェクトからの insert() の方は、もっとメモリを節約する方法があるような気がしますが・・・。
ところで、処理Aについては、メモリを大量に使うのは当然で、これは1万件すべてを一旦 EntityManager の UnitOfWork にためこんで、最後の flush() の時点で実際に DB に INSERT クエリーを発行する方式だからです。(だからと言って、Pure PHP Objectなのに 70MB かよ! とツッコみたくなりますが・・・)
このようなバッチ処理では、全部を UnitOfWork にためるのではなく、ある程度の件数ごとに flush() するような方法が Doctrine のドキュメントに書いてありました。
この方法で、バッチサイズを 1000、100、10、1 と変えて試してみました。コードは以下のようになります。
【処理A】 - エンティティをpersist - 特定の件数ごとに flush()
<?php $startTime = microtime(true); $em = $this->container->get('doctrine.orm.entity_manager'); for ($i = 0; $i < 10000; ++$i) { $address = new Address(); $address->setPrefName('岐阜県'); $address->setZip1('111'); $address->setZip2('2222'); $address->setAddr1('山奥の村一丁目一番地'); $em->persist($address); if ($i % 1000) { $em->flush(); $em->clear(); } } $em->flush(); echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
結果は以下のようでした。
なんだかよく分からない結果ですが、メモリの消費量は抑えられるようです。
主キーランダム SELECT 1万回
ランダムな主キーで 1 件のレコードを取得します。
EntityManager の find() メソッドを使う方法と、DQL を使う方法で試してみます。
【処理A】- EntityManager の find() メソッド
<?php $startTime = microtime(true); $em = $this->container->get('doctrine.orm.entity_manager'); for ($i = 0; $i < 10000; ++$i) { $address = $em->find('Application\HelloBundle\Entity\Address', (int)rand(1, 10000)); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
【処理B】 - DQL から
<?php $startTime = microtime(true); $em = $this->container->get('doctrine.orm.entity_manager'); $query = $em->createQuery('select a from Application\HelloBundle\Entity\Address a where a.id =:id'); $query->setMaxResults(1); for ($i = 0; $i < 10000; ++$i) { $query->setParameter('id', (int)rand(1, 10000)); $address = $query->getSingleResult(); } echo "proc time:" . (microtime(true) - $startTime) . 'ms' . PHP_EOL; echo "memory usage:" . (memory_get_peak_usage(true) / 1048576) . 'MB' . PHP_EOL;
処理Bでは、ハイドレーションしない、というオプションも使えます。
<?php $address = $query->getSingleResult(Query::HYDRATE_ARRAY);
※処理Aの方式で、ハイドレーションオプションを指定する方法は、調べていません・・・。
これらの処理の実行時間と、メモリ使用量は次のようになりました。
メモリ使用量は Doctrine1 と比べてやや増えていますが、速度は Doctrine1 でハイドレーションしないときと比べても相当速いですね。
まとめ(にはあいかわらずなっていませんが)
- Doctrine2 はパフォーマンスは向上しているっぽい
- けど使い方によってはメモリ消費はきつい
とりあえずいくつかやってみましたが、まだまだ Doctrine2 の使い方をよく分かっていません!
みなさんも実際に使ってみて、「こう書くといい」というやり方など、教えてください!
次回は、同じ検証を、Symfony2 & DoctrineODM(MongoDB) でやったものを掲載予定です。
また、この記事で使った Command の作り方と、Doctrine2 で MySQL を使う場合の注意点のようなことも、別記事でアップします。
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好きな有志で集まったチームです。