パフォーマンスプロファイル

LNTは、パフォーマンスプロファイルの保存と表示をサポートしています。これらのプロファイルの目的は、テストサンプル間のコード生成の違いを明らかにし、コードのホットセクションを簡単に識別できるようにすることです。

LNTにおけるプロファイルの原則

LNTのプロファイルは、カスタム形式で表現されます。ユーザーインターフェイスは、このカスタム形式へのクエリのみで動作します。アダプターは、他の形式からLNTのプロファイル形式に変換するために記述されます。プロファイルデータは、通常のJSONレポートの一部としてLNTサーバーにアップロードされます。

プロファイルデータの生成

プロファイルの生成は、Python API呼び出し(lnt profileがラッパー)を介して直接、またはlnt runtestsツールを使用して行うことができます。

lnt runtests test-suite を介したプロファイルデータの生成

LNTを介したプロファイル収集は、現在、Linuxのperfインフラストラクチャを使用するアダプターのみが記述されているため、Linuxシステムでのみサポートされています。より多くのアダプターが記述されたら、LNTはそれらのサポートを拡張できます。

テストシステムが既にlnt runtestsを使用してテストをビルドおよび実行している場合、プロファイルを生成する最も簡単な方法は、単一のパラメーターを追加することです。

--use-perf=all

--use-perfオプションは、Linux Perfを何に使用するかを指定します。オプションは次のとおりです。

  • noneperfを何も使用しません。

  • timeperfを使用してコンパイル時間と実行時間を測定します。これは、timeよりもはるかに正確です。

  • profile:プロファイリングのみにperfを使用します。

  • all:プロファイリングとタイミングの両方にperfを使用します。

生成されたプロファイルは、各テスト実行可能ファイルとともに、$TEST.perf_dataという名前で存在します。これらのプロファイルは、テストの実行終了時に処理され、LNTのプロファイル形式に変換され、生成されたreport.jsonに挿入されます。

lnt runtests test-suite なしでのプロファイルデータの生成

LNTのサポートされているユースケースの1つは、パフォーマンス追跡にLNTサーバーを使用することですが、実際にはテストのビルド、実行、および統計の収集にはlnt runtestsとは異なるテストドライバーを使用することです。

プロファイリングデータは、LNTに送信されたJSONレポート内に存在します。このセクションでは、既存のJSONレポートにプロファイルデータを追加する方法について説明します。JSONレポートの一般的な構造の詳細については、データのインポートを参照してください。

最初の手順は、JSON経由で送信するのに適したLNT形式でプロファイルデータ自体を生成することです。プロファイルをインポートするには、lnt profile upgradeコマンドを使用します。

lnt profile upgrade my_profile.perf_data /tmp/my_profile.lntprof

my_profile.perf_dataは、ここではLinux Perf形式であると想定されていますが、アダプターが登録されている任意の形式にすることができます(これは現在Linux Perfのみですが、時間の経過とともにより多くのものが追加されると予想されます)。

/tmp/my_profile.lntprofは、スペース効率の高いバイナリ形式のLNTプロファイルになりました。JSONを介して送信できるように準備するには、base-64エンコードする必要があります。

base64 -i /tmp/my_profile.lntprof > /tmp/my_profile.txt

これで、レポートに追加するだけです。プロファイルは、文字列データを持つサンプルであるという点で、ハッシュに似ています。

{
  "format_version": "2",
  "machine": {
     ...
  },
  "run": {
     ...
  },
  "tests": [
     {
         "name": "nts.suite1/program1",
         "execution_time": [ 0.1056, 0.1055 ],
         "profile": "eJxNj8EOgjAMhu99Cm9wULMOEHgBE888QdkASWCQFWJ8e1v04JIt+9f//7qmfkVoEj8yMXdzO70v/RJn2hJYrRQiveSWATdJvwe3jUtgecgh9Wsh9T6gyJvKUjm0kegK0mmt9UCjJUSgB5q8KsobUJOQ96dozr8tAbRApPbssOeCcm83ddoLC7ijMcA/RGUUwXt7iviPEDLJN92yh62LR7I8aBUMysgLnaKNFNzzMo8y7uGplQ4sa/j6rfn60WYaGdRhtT9fP5+JUW4="
     }
  ]
 }

サポートされている形式

Linux Perf

Perfプロファイルは、perfラッパーツールやLinux / GPLヘッダーを使用せずに、バイナリperf.dataファイルから直接読み取られます。これにより、プロファイルされたバイナリ/ライブラリが読み取り可能であることが予想されるため、デバッグにのみ役立ちますが、非Linuxプラットフォームでも実行可能になります。

perfインポートコードは、LNTプロジェクト用に作成されたcPerfと呼ばれるC ++拡張機能を使用します。perf annotateまたはperf reportよりも機能は劣りますが、約6倍速く、機械可読形式でほぼ同じデータを生成します。バイナリデータで効率的に動作する読みやすいPythonを記述するのは難しいため、C ++で記述されています。イベントストリームが集計されると、python辞書オブジェクトが作成され、処理はPythonに戻ります。プロファイルインポートは古いハードウェアやそれほど強力ではないハードウェアで実行される可能性があり、LLVMのテストスイートにはインポートする必要のある数百のテストが含まれているため、この段階では速度が重要です!

Perfの最近のバージョンでは、新しいサブコマンドperf dataが存在します。これは、CTF形式でイベントトレースを出力します。これは、babeltraceとそのPythonバインディングを使用してクエリできます。これにより、同様にパフォーマンスが高ければ、LNTのカスタムコードの多くを削除できます。

新しいプロファイル形式のサポートの追加

新しいプロファイルアダプターを作成するには、lnt.testing.profileパッケージに、ProfileImplクラスをサブクラス化する新しいPythonクラスを作成する必要があります。

class lnt.testing.profile.profile.ProfileImpl
static checkFile(fname)

「fname」がこのプロファイル実装のシリアル化されたバージョンである場合はTrueを返します。

static deserialize(fobj)

「fobj」からプロファイルを読み取り、新しいプロファイルオブジェクトを返します。これは遅延可能です。

getCodeForFunction(fname)

すべての呼び出しに対して3タプルを返すジェネレーターを返します。

(counters, address, text)

ここで、countersは辞書です。(例){'cycles': 50.0}、テキストはgetDisassemblyFormat()によって返される形式であり、addressは整数です。

カウンター値は、(関数の合計の)パーセンテージでなければならず、絶対数ではありません。

getDisassemblyFormat()

getCodeForFunction()によって返される逆アセンブリ文字列の形式を返します。可能な値は次のとおりです。

  • raw - 解釈は利用できません。

    純粋な文字列。

  • marked-up-disassembly - LLVM マークアップされた逆アセンブル形式。

getFunctions()

関数名と、その関数に関する情報を含む辞書を返します。

情報辞書には以下が含まれます。

  • counters - 関数のカウンタ値。

  • length - すべての命令を取得するためにgetCodeForFunctionを呼び出す回数。

辞書には、逆アセンブル/関数の内容は含めるべきではありません。カウンタ値は、絶対数ではなくパーセンテージである必要があります。

例:

{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0},
          'length': 200},
 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0},
            'length': 4}
}
getTopLevelCounters()

プロファイル全体のカウンタを含む辞書を返します。これらは絶対数になります。例:{'cycles': 5000.0}

getVersion()

プロファイルバージョンを返します。

serialize(fname=None)

プロファイルを指定されたファイル名(ベース)にシリアライズします。fnameがNoneの場合、bytesインスタンスとして返します。

static upgrade(old)

「old」内の以前のプロファイル実装を受け取り、このバージョン用の新しいProfileImplを返します。サポートする必要がある古いバージョンは、直前のバージョンのみです(例:バージョン3はバージョン2からのアップグレードのみを処理する必要があります)。

サブクラスは、指定されたすべての関数を実装するか、perf.pyが行うように、checkFile()deserialize()の静的関数のみを実装することができます。このモデルでは、deserialize()内で、プロファイルデータを単純な辞書構造に解析し、それからProfileV1Implオブジェクトを作成します。これは、辞書表現からのみ機能する非常にシンプルなプロファイル実装です。

class lnt.testing.profile.profilev1impl.ProfileV1(data)

ProfileV1ファイルは、決して複雑ではありません。それらは、プロダクション/消費のために最もわかりやすい方法でプロファイルデータがレイアウトされた単純なPythonオブジェクトであり、その後pickleされて圧縮されます。

それらは、単純にself.dataメンバーに格納することで作成されることが期待されます。

self.dataメンバーには次の形式があります。

{
 counters: {'cycles': 12345.0, 'branch-misses': 200.0}, # absolute values.
 disassembly-format: 'raw',
 functions: {
   name: {
     counters: {'cycles': 45.0, ...}, # Note counters are now percentages.
     data: [
       [463464, {'cycles': 23.0, ...}, '      add r0, r0, r1'}],
       ...
     ]
   }
  }
}
static checkFile(fn)

「fname」がこのプロファイル実装のシリアル化されたバージョンである場合はTrueを返します。

static deserialize(fobj)

「fobj」からプロファイルを読み取り、新しいプロファイルオブジェクトを返します。これは遅延可能です。

getCodeForFunction(fname)

すべての呼び出しに対して3タプルを返すジェネレーターを返します。

(counters, address, text)

ここで、countersは辞書です。(例){'cycles': 50.0}、テキストはgetDisassemblyFormat()によって返される形式であり、addressは整数です。

カウンター値は、(関数の合計の)パーセンテージでなければならず、絶対数ではありません。

getDisassemblyFormat()

getCodeForFunction()によって返される逆アセンブリ文字列の形式を返します。可能な値は次のとおりです。

  • raw - 解釈は利用できません。

    純粋な文字列。

  • marked-up-disassembly - LLVM マークアップされた逆アセンブル形式。

getFunctions()

関数名と、その関数に関する情報を含む辞書を返します。

情報辞書には以下が含まれます。

  • counters - 関数のカウンタ値。

  • length - すべての命令を取得するためにgetCodeForFunctionを呼び出す回数。

辞書には、逆アセンブル/関数の内容は含めるべきではありません。カウンタ値は、絶対数ではなくパーセンテージである必要があります。

例:

{'main': {'counters': {'cycles': 50.0, 'branch-misses': 0},
          'length': 200},
 'dotest': {'counters': {'cycles': 50.0, 'branch-misses': 0},
            'length': 4}
}
getTopLevelCounters()

プロファイル全体のカウンタを含む辞書を返します。これらは絶対数になります。例:{'cycles': 5000.0}

getVersion()

プロファイルバージョンを返します。

serialize(fname=None)

プロファイルを指定されたファイル名(ベース)にシリアライズします。fnameがNoneの場合、bytesインスタンスとして返します。

static upgrade(old)

「old」内の以前のプロファイル実装を受け取り、このバージョン用の新しいProfileImplを返します。サポートする必要がある古いバージョンは、直前のバージョンのみです(例:バージョン3はバージョン2からのアップグレードのみを処理する必要があります)。

プロファイルの表示

プロファイルがLNTに送信されると、手動URLまたは「実行」ページから利用できるようになります。

実行結果ページでは、プロファイルデータが利用可能な場合、テーブル行にマウスオーバーすると「プロファイルの表示」リンクが表示されます。

このマウスオーバー効果はタッチスクリーンフレンドリーではなく、直感的ではない可能性があることがわかっています。このページは、プロファイルデータリンクをより明確にするために、近日中に変更される予定です。

または、URLを手動で構築することでプロファイルを表示できます。

db_default/v4/nts/profile/<test-id>/<run1-id>/<run2-id>

ここで

  • test-id は、表示するテストのデータベースTestIDです。

  • run1-id は、表示の左側に表示する実行のデータベースRunIDです。

  • run2-id は、表示の右側に表示する実行のデータベースRunIDです。

明らかに、このURLは構築するのがやや難しいため、上記のように実行ページのリンクを使用することをお勧めします。