The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

パフォーマンスの測定

このガイドでは以下をカバーしています:

  • メモリ使用量の測定方法

  • 起動時間の測定方法

  • Quarkusがデフォルトでネイティブイメージに適用する追加フラグ

  • ツールにおける協調的省略(Coordinated omission)問題

すべてのテストは、バッチに対して同じハードウェアで実行しています。言うまでもないことですが、言うべきことです。

メモリ使用量の測定方法

Quarkusアプリケーションのフットプリントを測定する際には、 Resident Set Size(RSS) を測定します。JVMのヒープサイズではありません。JVMヒープサイズは全体的な問題のほんの一部です。JVMは、ヒープ用のネイティブメモリー( -Xms , -Xmx )を割り当てるだけでなく、アプリケーションを実行するためにjvmが必要とする構造体も割り当てます。JVMの実装に応じて、アプリケーションに割り当てられる総メモリーは、以下のものを含みますが、これに限りません。

  • ヒープ空間

  • クラスメタデータ

  • スレッドスタック

  • コンパイルされたコード

  • ガベージコレクション

ネイティブメモリのトラッキング

JVMで使用されているネイティブメモリを表示するには、hotspotでNMT( Native Memory Tracking )機能を有効にします;

コマンドラインでNMTを有効に設定;

-XX:NativeMemoryTracking=[off | summary | detail] (1)
1 注意: この機能は、約 5-10% のパフォーマンスオーバーヘッドを発生させます。

そして、jcmdを使ってアプリケーションを実行しているHotspot JVMのネイティブメモリ使用量のレポートをダンプすることができます;

jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB]

クラウドネイティブのメモリ制限

Cloud Nativeアプリケーションの影響を見るためには、メモリー全体を測定することが重要です。特にRSSメモリーの全使用量に基づいてプロセスを強制終了させてしまうことがあるコンテナー環境では特にそうです。

同様に、プロセスが使用している、他のプロセスと共有されないプライベートメモリだけを測定するという罠には陥らないようにしましょう。プライベートメモリは、多くの異なるアプリケーションをデプロイする環境(メモリを多く共有する)では有用かもしれませんが、Kubernetes/OpenShiftのような環境では非常に誤解を招く可能性があります。

Docker上でメモリを正しく測定する

メモリを正しく測定するためには、 docker statやそれに由来するもの(ctopなど)を使用しないでください 。この方法では、使用中のレジデントページのサブセットしか測定できません。一方、Linuxカーネル、cgroups、およびクラウドオーケストレーションプロバイダは、アカウンティング(プロセスが制限を超えているかどうか、強制終了すべきかどうかの判断)に完全なレジデントセットを利用します。

正確に測定するためには、Linux上でRSSを測定するのと同様の手順を実行する必要があります。docker top コマンドは、コンテナインスタンス内のプロセスに対して、コンテナホストマシン上で ps コマンドを実行することができます。これを出力パラメータのフォーマットと組み合わせて利用することで、RSSの値を返すことができます:

docker top <CONTAINER ID> -o pid,rss,args

例えば、以下のようになります:

 $ docker top $(docker ps -q --filter ancestor=quarkus/myapp) -o pid,rss,args

PID                 RSS                 COMMAND
2531                27m                 ./application -Dquarkus.http.host=0.0.0.0

また、特権シェル(ホスト上のroot)に直接ジャンプして、 ps コマンドを直接実行することもできます:

 $ docker run -it --rm --privileged --pid=host justincormack/nsenter1 /bin/ps -e -o pid,rss,args | grep application
 2531  27m ./application -Dquarkus.http.host=0.0.0.0

Linuxを使用している場合は、シェルがコンテナホストと同じであるため、 ps コマンドを直接実行することができます:

ps -e -o pid,rss,args | grep application

プラットフォーム固有のメモリレポート

NVMを有効にして実行する際のパフォーマンスオーバーヘッドを発生させないために、各プラットフォームに特化したツールを使用して、JVMアプリケーションの総RSSを測定します。

Linux

Linux の pmapps ツールは、プロセスのネイティブメモリマップのレポートを提供します

 $ ps -o pid,rss,command -p <pid>

   PID   RSS COMMAND
 11229 12628 ./target/getting-started-1.0.0-SNAPSHOT-runner
 $ pmap -x <pid>

 13150:   /data/quarkus-application -Xmx100m -Xmn70m
 Address           Kbytes     RSS   Dirty Mode  Mapping
 0000000000400000   55652   30592       0 r-x-- quarkus-application
 0000000003c58000       4       4       4 r-x-- quarkus-application
 0000000003c59000    5192    4628     748 rwx-- quarkus-application
 00000000054c0000     912     156     156 rwx--   [ anon ]
 ...
 00007fcd13400000    1024    1024    1024 rwx--   [ anon ]
 ...
 00007fcd13952000       8       4       0 r-x-- libfreebl3.so
 ...
 ---------------- ------- ------- -------
 total kB         9726508  256092  220900

プロセスに割り当てられた各メモリ領域が一覧表示されます;

  • Address: 仮想アドレス空間の開始アドレス

  • Kbytes(キロバイト): リージョン用に予約された仮想アドレス空間のサイズ (キロバイト)

  • RSS:レジデント・セット・サイズ(キロバイト)。実際に使用されているメモリ領域の大きさを示します。

  • Dirty:ダーティページ(共有とプライベートの両方)、キロバイト単位

  • Mode: メモリ領域のアクセスモード

  • Mapping: プロセス用のアプリケーション領域と共有オブジェクト(.so)のマッピングを含む

Total RSS (kB)の行は、プロセスが使用しているネイティブメモリの合計を報告します。

macOS

macOSでは、 ps x -o pid,rss,command -p <PID> と指定すると、指定したプロセスのRSSをKB(1024バイト)単位で一覧表示することができます。

$ ps x -o pid,rss,command -p 57160

  PID    RSS COMMAND
57160 288548 /Applications/IntelliJ IDEA CE.app/Contents/jdk/Contents/Home/jre/bin/java

つまり、IntelliJ IDEAは281,8MBのRSSを消費していることになります。

起動時間の測定方法

フレームワークの中には、アグレッシブな遅延初期化技術を使用しているものがあります。フレームワークを開始するために必要な時間を最も正確に反映させるために、最初のリクエストまでの起動時間を測定することが重要です。そうしないと、フレームワークが 実際に 初期化にかかる時間を見逃してしまいます。

ここでは、テストにおける起動時間の測定方法を説明します。

アプリケーションのライフサイクルのある時点のタイムスタンプを記録するサンプルアプリケーションを作成します。

@Path("/")
public class GreetingEndpoint {

    private static final String template = "Hello, %s!";

    @GET
    @Path("/greeting")
    @Produces(MediaType.APPLICATION_JSON)
    public Greeting greeting(@QueryParam("name") String name) {
        System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new java.util.Date(System.currentTimeMillis())));
        String suffix = name != null ? name : "World";
        return new Greeting(String.format(template, suffix));
    }

    void onStart(@Observes StartupEvent startup) {
        System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new Date()));
    }
}

シェル内でループを開始し、テストしているサンプルアプリケーションのRESTエンドポイントにリクエストを送信します。

$ while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:8080/api/greeting)" != "200" ]]; do sleep .00001; done

別のターミナルで、テストしているタイミングアプリケーションを起動し、アプリケーションが起動した時刻表示します

$ date +"%T.%3N" &&  ./target/quarkus-timing-runner

10:57:32.508
10:57:32.512
2019-04-05 10:57:32,512 INFO  [io.quarkus] (main) Quarkus 0.11.0 started in 0.002s. Listening on: http://127.0.0.1:8080
2019-04-05 10:57:32,512 INFO  [io.quarkus] (main) Installed features: [cdi, rest, rest-jackson]
10:57:32.537

最終タイムスタンプと最初のタイムスタンプの差は、アプリケーションが最初のリクエストにサービスを提供するまでの総起動時間です。

Quarkusで適用される追加フラグ

QuarkusがGraalVM native-image を起動すると、 デフォルトでいくつかの追加フラグが適用されます。

他のビルドと性能特性を比較する場合には、以下のようなものを把握しておくとよいです。

フォールバックイメージの無効化

フォールバックネイティブイメージは、何らかの理由でネイティブコードへのコンパイルに失敗した場合に、アプリケーションを通常のJVMで実行するために「フォールバック」するためのGraalVMの機能です。

Quarkusは、 -H:FallbackThreshold=0 を設定することでこの機能を無効にします。これにより、アプリケーションが本当にネイティブモードで実行できないことに気づかないリスクの代わりに、コンパイルに失敗するように出来ます。

その代わりに Java モードで実行したい場合は、ネイティブイメージのビルドをスキップして jar として実行することも可能です。

分離の無効化

分離はGraalVMの優れた機能ですが、Quarkusは現段階では使用していません。

-H:-SpawnIsolates で無効化します。

すべてのサービスローダー実装の自動登録を無効にする

Quarkusのエクステンションでは、必要なサービスを自動的に選択することができますが、GraalVMのネイティブイメージのデフォルトでは、クラスパス上で見つけることができるすべてのサービスが含まれています。

より最適化されたバイナリを生成するために、明示的にサービスをリストアップすることをお勧めします。 -H:-UseServiceLoaderFeature により、これを無効にすることも可能です。

その他…​

このセクションは、高レベルのガイダンスとして提供されています。フラグの中には、エクステンションや構築しているプラットフォーム、設定の詳細、あなたのコード、そして場合によってはこれらの組み合わせによって動的に制御されものがあるため、このセクションは包括的なものではありません。

一般的に言えば、ここに挙げたものはパフォーマンス・メトリクスに影響を与える可能性が高いものですが、適切な状況下では、他のフラグによる影響も無視できないものになるでしょう。

いくつかの差異を詳細に調査する場合は、Quarkusが呼び出しているものを正確にチェックしてください。ビルドプラグインがネイティブイメージを生成しているときには、コマンドライン全体がログに記録されます。

ツールにおける協調的省略(Coordinated omission)問題

Quarkusのようなフレームワークのパフォーマンスを測定する場合、ユーザーが経験するレイテンシーは特に注目の対象で、そのためにさまざまなツールがあります。残念なことに、多くのツールはレイテンシを正しく測定することができず、協調的省略問題を引き起こしています。つまり、ツールは、システムが負荷下にあるときに新しいリクエストを送信する遅延に適応することに失敗し、これらの数値を集計するため、待ち時間とスループットの数値が非常に誤解を招きやすくなります。

このビデオ で、wrk2 の作者である Gil Tene がこの問題について説明しています。 Quarkus Insights #22 では、Quarkus パフォーマンス チームの John O’Hara が、それがどのように現れるか説明しています。

そのビデオと関連する論文や記事の日付はすべて2015年にさかのぼりますが、現在でも、協調的省略の問題で不十分なツールを見つけることができます。

現時点では、この問題を引き起こすことが知られているツールは、レイテンシ/スループットの測定には使用しないでください(他のことには使用できるかもしれません):

  • JMeter

  • wrk

影響を受けないことが確認されているツールは以下の通りです:

したがって、 wrk2hyperfoil を使う場合でも、その数値が何を意味するのかを理解するようにしてください。

関連コンテンツ