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

パフォーマンスの測定

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

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

  • 起動時間の測定方法

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

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

メモリ使用量の測定方法

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] [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: ダーティなページ (共有およびプライベートの両方) をキロバイト単位で表示します。

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

  • マッピング: プロセス用のアプリケーション領域と共有オブジェクト(.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, resteasy-reactive, resteasy-reactive-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 により、これを無効にすることも可能です。

ガベージコレクション実装のためのより良いデフォルト値

GraalVMのデフォルト値は、短命なプロセスに最適化することを意図していると思われます。

Quarkusのデフォルトはサーバーアプリケーションになっているので、 -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime によってより良いデフォルト値に切り替えています。

その他…​

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

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

差異を詳細に調査する場合は、Quarkusが何を呼び出しているかを正確に確認する必要があります。ビルドプラグインがネイティブイメージを生成する際には、コマンドライン全体がログに記録されます。