LoginSignup
7
2

More than 5 years have passed since last update.

BEAR.Sunday内部のベンチマーク

Last updated at Posted at 2017-12-15

helloworldベンチマークというフレームワークのミニマムブートストラップを計測するベンチマークがあります。この記事ではBEAR.Sundayでのブートスラップコストをより細かく分割して、それぞれ内部のアクションにどれくらいのいのコストがかかっているかを見ます。

アプリケーションはユーザーのプロジェクトフォルダのbootstrap/bootstrap.phpで、リクエストの最初から最後まで実行されます。

image.png

action 意味
load composerのautoload初期化
$app ルートオブジェクトグラフの取得(bootstrap.phpのみで使用)
route webリクエストとオブジェクトリクエストの変換
request リソースリクエスト
transfer リソース状態を表現に変換してクライアントへの転送

インストール直後のスケルトンアプリケーションにベンチマーク用の関数t()とその結果を表示するresult()を追加して、それぞれの時間を計測しました。

git clone https://github.com/koriym/BEAR.HelloworldBenchmark.git
cd BEAR.HelloworldBenchmark
composer install --no-dev
composer dump-autoload --no-dev
php -S 127.0.0.1:8080/ bootstrap/bench.php
bootstrap.php
<?php
use BEAR\Package\Bootstrap;
use BEAR\Resource\ResourceObject;

$context = 'prod-app';

apcu_add('i', 0);
apcu_store('i', apcu_fetch('i') + 1);

require dirname(__DIR__) . '/autoload.php';
t('load');

/* @global string $context */
$app = (new Bootstrap)->getApp('MyVendor\MyProject', $context, dirname(__DIR__));
t('app');

$request = $app->router->match($GLOBALS, $_SERVER);
t('route');

try {
    $page = $app->resource->{$request->method}->uri($request->path)($request->query);
    t('request');

    /* @var $page ResourceObject */
    $page->transfer($app->responder, $_SERVER);
    t('transfer');
    if (isset($_GET['result'])) {
        result();
    }

    exit(0);
} catch (\Exception $e) {
    $app->error->handle($e, $request)->transfer();
    exit(1);
}

function t(string $key)
{
    static $i;

    apcu_add($key, 0);
    $i = $i ?: apcu_fetch('i');
    if ($i === 1) {
        return; // first call to create cache
    }
    $time = apcu_fetch($key) + microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
    apcu_store($key, $time);
}

function result()
{
    $i = apcu_fetch('i');
    $averageRequestTime = apcu_fetch('transfer') / $i;
    $lastTimer = 0;
    foreach (['load', 'app', 'route', 'request', 'transfer'] as $action) {
        $actionTime = apcu_fetch($action);
        $elapsedTime = number_format($actionTime / $i * 1000, 3);
        $periodTime = number_format(($actionTime - $lastTimer) / $i * 1000, 3);
        $lastTimer = $actionTime;
        $proportion = number_format($periodTime / $averageRequestTime / 1000 * 100, 1);
        printf("| %s | %s | %s | %s%% |\n", $action, $elapsedTime, $periodTime, $proportion);
    }
}
action 経過時間 所要時間 割合
load 1.058 1.058 12.1%
app 7.502 6.443 73.5%
route 7.564 0.062 0.7%
request 8.725 1.161 13.2%
transfer 8.764 0.039 0.4%

composerのautoloadの初期化12%。routetransferは無視できるほど小さく、$appオブジェクトのアンシリアライズが約3/4とほとんどを占めています。

次に最適化オプションをつけてautoloadをダンプします。

composer dump-autoload --no-dev -o
action 経過時間 所要時間 割合
load 1.998 1.998 21.6%
app 8.068 6.070 65.6%
route 8.125 0.057 0.6%
request 9.212 1.087 11.8%
transfer 9.251 0.038 0.4%

autoloadの初期化に倍の時間がかかるようになってしまいました。不使用かもしれないクラスマップを読み込んでるためです。初期化コストを払うからわりにランタイムでのオートロードの時のファイルパスを探すのが早くなります。apprequestに注目してください。僅かに早くなってるのはランタイムでのオートロードコストが低いためと考えられます。$appをアンシリアライズする課程で大量のクラスファイルをロードしないといけないからです。

このベンチマークのようなランタイム中にオートロードの必要の少ないプログラムでは逆に遅くなることになります。その差はこのhello worldが最大と考えていいでしょう。約5%程度です。

composer dump-autoload --no-dev

Polidog.Todo

HTML

次にHTMLアプリケーションを検討します。TwigAura.Sqlを使ったアプリケーションで実際にデータベースもアクセスしていますす。

image.png

-oなし

action 経過時間 所要時間 割合
load 1.221 1.221 3.3%
app 9.841 8.620 23.2%
route 9.937 0.096 0.3%
request 21.521 11.584 31.1%
transfer 37.230 15.710 42.2%

-oあり

action 経過時間 所要時間 割合
load 3.036 3.036 8.2%
app 11.019 7.984 21.6%
route 11.105 0.085 0.2%
request 21.973 10.868 29.4%
transfer 36.921 14.948 40.5%

最適化オプションのあるなし(-o)での違いを見るとやはりhello worldアプリと同じ傾向がみられます。loadの初期化コストとapp+requestがトレードオフの関係にあります。

transferでは毎回Twigのテンプレートをレンダリングしているのでこのようなコストになりました。

API

最後にAPIです。先ほどのTodoアプリと違ってテンプレートのレンダリングはないがDBアクセスを行ってJSONを出力しています。

API -oなし

action 経過時間 所要時間 割合
load 1.247 1.247 7.2%
app 10.085 8.838 50.8%
route 10.183 0.098 0.6%
request 17.341 7.158 41.2%
transfer 17.391 0.051 0.3%

API -oあり

action 経過時間 所要時間 割合
load 5.596 5.596 27.5%
app 13.567 7.971 39.1%
route 13.654 0.088 0.4%
request 20.329 6.675 32.7%
transfer 20.383 0.054 0.3%

helloworldとHTMLの中間ぐらいの数字です。-oオプションをつけないでautolodaerをダンプした方が有利な結果になりました。

BEAR.Sundayのbootstrapの特徴

オブジェクトシリアライズとPHPファクトリーコードの生成により、DIコンテナや各機能コンポーネントの初期化コストが原理的にほぼないことです。モジュール追加で機能追加してもbootstrapコストはほとんど変わりません。

「フレームワークの機能が多いから遅い/少なから速い」ということがありません。

アプリケーションタイプによるコストインパクト

フレームワークのbootstrapコスト(hello wolrdベンチ)のインパクトはアプリケーションや用途によって大きく変わります。

データベースアクセスを用いてHTMLを返すWebアプリケーションでは相対的に低いです。単純なTodoアプリの場合で初期化に27%、リクエスト(データの作成)に32%、TwigでHTMLにするのに42%かかっています。(この場合Helloworldベンチを倍の性能にしても13.5%しか速度向上しません)

DBなどに単純なアクセスをしてJSONを返すようなAPIの場合にはアプリケーションコードよりフレームワークコストのbootstrapコストの方が高くなる場合もあります。(ちょっと納得できかねますが)

dump-autoload -o オプション

実験の結果からは-oオプションはBEAR.Sundayアプリケーションではつけない方が良さそうですが、念のために実際のアプリケーションでベンチをとってみたら良いと思います。

autoload.phpコンパイル

2018/05/19にリリースしたBEAR.Package v1.7.11のautoloadコンパイルのベンチマークも取ってみます。このautoload.phpのようなファイルを削減してautoloadの時のクラスファイルの読み込みコストを大幅に低減します。

コンパイルなし

action 経過時間 所要時間 割合
load 3.015 3.015 39.7%
app 6.856 3.841 50.6%
route 6.917 0.061 0.8%
request 7.582 0.665 8.8%
transfer 7.594 0.012 0.2%

Requests per second: 123.11 [#/sec] mean
Time per request: 81.230 [ms] mean
Time per request: 8.123 [ms] mean, across all concurrent requests

コンパイルあり

action 経過時間 所要時間 割合
load 6.490 6.490 91.3%
app 6.728 0.239 3.4%
route 6.791 0.062 0.9%
request 7.097 0.306 4.3%
transfer 7.109 0.011 0.2%

Requests per second: 135.79 [#/sec] mean
Time per request: 73.642 [ms] (mean
Time per request: 7.364 [ms] (mean, across all concurrent requests

apprequestなどの各処理の所用時間が減っているのはその時に必要なオートロードがほぼ無しになったためです。$appをアンシリアライズする時間も1/10以下になっています。

大きなオブジェクトのアンシリアライズが遅いのではなく、実はその際に発生するオートロードに時間がかかっていたということが分かりました。(=バイナリシリアライズオプションとか導入しても効果は限定的と思われます)

改めてはっきりしたのはBEAR.Sundayのミニマムブートストラップのうちの90%以上はrequire実行のコストということです。一方、他の処理が十分に最適化されているということも言えるかと思います。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2