ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Metalシェーダのパフォーマンスを改善するベストプラクティス
Apple GPUの最新技術を利用してMetalシェーダのパフォーマンスを向上させる方法を紹介します。関数定数を設定してシェーダの実行時間を短縮する方法と、関数グループを使用してコンパイラの最適化を高める方法を学びます。リソースを並列使用する機能とシェーダの実行を改善することで実行時間を短縮する方法を確認しましょう。Apple family 9 GPUの機能についての理解を深め、ハードウェアアクセラレーションをレイトレーシングに活用しましょう。
リソース
関連ビデオ
Tech Talks
WWDC23
WWDC20
-
ダウンロード
こんにちは Srividya Karumuriです Appleの GPUコンパイラエンジニアです 本日はMetalシェーダの パフォーマンスを 向上させるヒントをいくつか紹介します
M3とA17 Proの 新しいApple family 9 GPUは いくつかの点で進化しており アプリにも適用できる場合があります Apple family 9 GPUに関して 推奨事項がいくつかあります また すべてのApple GPU世代に 適用できるヒントも紹介します
Metalシェーディング言語の機能を使用して 実行時間を短縮することで シェーダのパフォーマンスを向上できます シェーダのリソース使用率を改善すると 並列性が
向上し Apple family 9 GPUのレイトレーシングアクセラレーション ハードウェアを最大限に活用できます
Metalにはシェーダの実行時間を 最小限に抑えることができる 機能がいくつかあります 関数定数でシェーダを効率的に 特殊化できます 関数グループでは間接関数呼び出しを 使用してシェーダを最適化できます
Metalの関数定数機能は シェーダを効率的に特殊化し 実行時に到達できないコードを削除します 例えば ウーバーシェーダでは通常 関数定数が役立ちます
多くの場合 ウーバーシェーダは複雑になります 3Dアプリで多様な マテリアルタイプをレンダリングするなど 実行時に様々な処理を行うことが あるためです
デベロッパはバッファから マテリアルパラメータを読み取る ウーバーシェーダを作成する場合があります その場合 マテリアルシェーダが 実行時にバッファの内容に基づいて 異なる制御部分を選択します
このアプローチではバッファ内の パラメータのみが変更されるため シェーダは再コンパイルせずに 新しいマテリアルエフェクトを レンダリングできます
例えば パイプライン内の このウーバーシェーダは 描画コマンドのMetalバッファに trueに等しいis_glossy パラメータがあるため 光沢のあるマテリアルをレンダリングします バッファのis_glossyパラメータが falseに等しい場合 同じシェーダでマットマテリアルも レンダリングできます
動作の変更はバッファ内の内容に 基づいて行われるため レンダリングパイプラインは両方の マテリアルエフェクトで同じです
このレスポンシブアプローチは 開発中には便利ですが シェーダではいくつかの可能性を考慮し 追加のバッファから読み取る 必要があるため アプリのパフォーマンスに 影響を与える可能性があります もう1つのアプローチでは 実行時ではなくコンパイル時に シェーダを特殊化します プリプロセッサマクロを使用して シェーダバリアンスを オフラインでビルドします
これはマクロを使用して特殊化した ウーバーシェーダです
特殊化された各シェーダには 独自のレンダリングパイプラインがあり 特定のマテリアルエフェクトの レンダリングに必要なコードのみが 含まれています
このアプローチでは 考えられる すべてのバリアントの組み合わせを オフラインでコンパイルする 必要があります 例えば 光沢のバリアントでは is_glossyマクロとuse_shadowsマクロの 両方を有効にし 残りのマクロを無効にします
同様に マット関数のバリアントは use_shadowsマクロと has_reflectionsマクロの組み合わせに なる可能性があります
また 光沢のある反射バリアントでは is_glossyマクロと has_reflectionsマクロを有効にします ほかも同様です
マクロを使用して ウーバーシェーダを実装する場合 考えられるマクロの組み合わせごとに 1つのバリアントというように 多数のバリアントのコンパイルが 必要になる場合があります その中には アプリでは 使用されないものもあります
事前にオフラインでコンパイルしても 各バリアントが積み重なり Metalライブラリのサイズが 大幅に増加する可能性があります またバリアントの各シェードは Metalソースからコンパイルする 必要があるため コンパイル時間が長くなる可能性もあります
関数定数では シェーダを特殊化する 別の方法が使用できます マクロを使用する場合に比べて コンパイル時間と Metalライブラリサイズの 両方を低減できます 関数定数を使用する場合 ソースからウーバーシェーダを 1回コンパイルして 中間のMetal関数を作成します その関数から 定義した関数定数に基づいて アプリに必要なバリアンスを作成します
関数定数を使用すると 複数の特殊化されたバリアントを 必要に応じて作成したり 中間のMetal関数を再利用して 残りのすべての可能性に対応したりする 柔軟性が得られます
このアプローチでは 必要なシェーダバリアンスと レンダリングパイプラインのみを 作成することで 時間と容量を節約できます
これらの特殊化された バリアンスを作成するには Metal関数コードで関数定数を宣言し Metal関数を作成する際に それぞれの値を定義します
関数定数を使用して 定数アドレス空間で宣言した プログラムスコープ変数を 初期化することもできます
Metalバッファから値を読み取る代わりに これらの関数定数を使用して シェーダ内の様々なコード部分を 有効にできます
関数定数を使用すると シェーダの 特殊化バリアントをコンパイルする際に Metalは関数定数を 定数ブール値として畳み込むことができます
その他の最適化として 到達できないコード部分を 排除することもできます
これにより 未使用の制御フローを 削除できます
関数定数を使用してシェーダを 特殊化することで バッファからマテリアルパラメータを クエリする必要がなくなります このアプローチでは 制御フローを簡素化し 未使用のコード部分を削除することで シェーダの実行時間が短縮されます
実行時に関数定数の値を 設定する方法の詳細は 「Metalを使用した GPUレンダリングの最適化」を ご覧ください その中では 同期コンパイルにおける ランタイムコンパイルのオーバーヘッドを 軽減する方法についても説明しています
間接的な関数呼び出しに 関数グループの属性を追加することで シェーダの実行時間を 短縮することもできます
間接関数とは シェーダが関数名で 直接呼び出すのではなく 関数ポインタや 識別可能な関数テーブルなどを 使用して呼び出す関数です
シェーダは静的または動的リンクを介して 間接関数を呼び出すことができます 間接関数呼び出しにより コードが拡張可能になり アプリでは多くのオプションを使用でき 柔軟性が高まります ただし 間接関数呼び出しを使用すると Metalでシェーダを十分に 最適化できない場合があります 特に呼び出しサイトに関する 部分がこれに該当します
静的にリンクされた関数の場合 Metal関数グループの機能を使用できます これにより 間接関数呼び出しを使用する シェーダをMetalで最適化できます
このシェーダは 照明とマテリアルの 関数ポインタを介した 呼び出しなど 3つの異なる間接関数を 呼び出します Metalでは シェーダが呼び出す関数を 識別できないため これらの関数ポインタ 呼び出しサイトにわたっての 最適化はできません ただし 関数ポインタが 特定のグループの1つの関数のみを 指すとわかっている場合は 関数グループの属性を使用できます 例えば シェーダが呼び出すことが できる唯一の関数は シェーダパイプライン状態にあり リンクされたすべての関数です
またlighting関数がarea関数 spot関数 またはsphere関数のみを 呼び出すことができると わかっているとします その場合 これらの関数を lighting関数グループに グループ化できます 同様に material関数ポインタがwood関数 glass関数 またはmetal関数のみを 呼び出すことができる場合 それらをmaterial関数グループに グループ化できます
関数グループの属性を呼び出しサイトに 追加することで 間接呼び出しを 最適化する方法のヒントを Metalに提供できます
関数グループを定義するには リンクされた関数グループの プロパティに辞書を 割り当てます 各辞書エントリでは文字列キーに 関数グループの名前が指定されており 値はそのグループに属する 関数の配列です このアプローチは静的にリンクする 関数にのみ効果があり バイナリライブラリとしてコンパイルする 関数に対してはメリットがありません
Metal関数ポインタと 様々なコンパイルワークフローの 詳細については ここに挙げた2つのビデオをご覧ください
ここまでシェーダの実行時間を短縮する 2つの方法を説明しました 関数定数を使用するとシェーダの 特殊化されたバリアントを 効率的に作成できます 関数グループを使用すると間接関数を 呼び出すシェーダを最適化できます
シェーダの実行時間を短縮する いくつかのMetal機能を見たところで 次に リソース使用率を改善して 並列性の向上につなげる 方法をいくつか説明します
スレッド占有率を増やすことは シェーダ実行時の 遅延隠蔽を改善するために 非常に重要です
スレッド占有率は 実際にはレジスタやメモリなど 利用可能なリソースの量に依存します
そのため シェーダからのデータ 使用を最適化すると スレッド占有率が増える可能性があります
Apple family 9 GPUには 占有率管理に関連する 新機能があります 詳細については 「M3とA17 ProでのGPUの進化」を ご覧ください
また スレッド占有率低下の ボトルネックを選別する 方法については 「M3とA17 Proの新しいMetal プロファイリングツール」をご覧ください
メモリオブジェクトのアドレス空間と ALUの演算で使用されるデータ型は リソース使用率に影響を与える 可能性があります
メモリオブジェクトに適した アドレス空間の選択は メモリ使用率の向上と スレッド占有率の改善のために 非常に重要です
Metalシェーディング言語では アドレス空間は 様々なアクセスパターンをサポートし
メモリオブジェクトが割り当てられる メモリ領域を 指定するように設計されます
適切なアドレス空間の選択は シェーダのパフォーマンスに 直接影響します ここでは 定数アドレス空間 デバイスアドレス空間 スレッドグループアドレス空間に 注目します 定数アドレス空間を使用すると 読み取り専用のメモリオブジェクトを 作成できます
これらのアクセスはすべての スレッドソフトウェアディスパッチや 描画で一定のデータに対して 最適化されます
オブジェクトのサイズが固定である場合や オブジェクトが異なるスレッドによって 何度も読み取られる場合 それらのオブジェクトを 定数アドレス空間で作成してください
読み取り/書き込みバッファは デバイスアドレス空間に作成できます アクセスされるデータが スレッド間で異なる場合 またはバッファのサイズが固定でない場合 そのようなバッファは デバイスアドレス空間に作成できます
定数アドレス空間とデバイスアドレス空間の 推奨事項と例については 「Metal Performanceを Appleシリコン搭載のMacに 最適化する」をご覧ください
スレッドグループ アドレス空間も 読み取り/書き込みメモリ オブジェクトに使用できます スレッドグループ内のスレッドは スレッドグループメモリ内の データを共有して連携できます
多くの場合 この方法は より高速に動作します
場合により スレッドグループメモリは デバイスバッファ/定数バッファの ソフトウェア管理キャッシュに使用されます 例えば デバイスメモリのブロックが スレッドグループメモリに コピーされて使用されます そのほうが高速になる場合があります
Apple family 9 GPUの シェーダコードメモリの新機能により スレッドグループメモリを 使用するタイミングに関する トレードオフが以前のGPUとは 異なる場合があります
シェーダでスレッドグループメモリを 主にデバイスバッファ/定数バッファの ソフトウェア管理キャッシュ として使用する場合 スレッドグループメモリにコピーするよりも これらのバッファから直接読み込んだほうが 効率的であることがあります
Apple family 9 GPUの 動的シェーダコアメモリ機能と 柔軟なオンチップメモリ機能により スレッドグループ/デバイス/ 定数の各メモリタイプは 同じキャッシュ階層を使用します そのため ワーキングセットのサイズが キャッシュに収まる場合 バッファとスレッドグループのメモリ アクセスの両パフォーマンス特性が 同等になる可能性があります その場合 スレッドグループ/ デバイス/定数アドレス空間に メモリのコピーを作成する代わりに シェーダを デバイスバッファまたは 定数バッファでのみ動作させ スレッドグループメモリへのコピーに伴う 遅延を回避できます
また データをデバイスバッファ または定数バッファ内だけに 保持することにメリットがあるかどうかは XcodeのMetalデバッガを 使用してワークロードを プロファイリングすることで評価できます
アドレスベースの選択と同様に データ型もパフォーマンスに 影響を与える可能性があります 例えば 16ビットデータ型は レジスタとメモリの フットプリント削減に役立つ場合があります
可能な場合 floatやintではなく halfやshortなどの 16ビットデータ型を使用すると パフォーマンスが向上します 変換に制限はないため 型変換はhalfとfloatの間などでも 問題ありません bfloatはfloatの16ビット 切り捨てバージョンで
機械学習アプリの高速化に最適です
低い精度で広い範囲の値を 扱うことができます
bfloatデータ型はMetal 3.1以降で サポートされています アプリの精度要件がbfloatで サポートされているものと 一致する場合 このデータ型の使用を 強くお勧めします
32ビットデータ型ではなく 16ビットデータ型を使用すると シェーダが使用するレジスタの数が減ります そのデータをメモリに保存する場合 メモリフットプリントの削減や 帯域幅の改善にも役立ちます その結果 スレッド占有率が 向上する可能性があります 16ビットデータ型を使用すると エネルギー効率も向上します
半分の精度で評価されることを意図した式を 記述する場合は 必ずリテラルにサフィックス「h」を 使用してください そうしないと 式全体がfloat精度で評価され 小さいサイズの型を使用するメリットが 失われます
一部のシェーダでは float型 half型 int型の命令を組み合わせるなど half型を使用することで 命令の組み合わせがより適切に 処理される場合があります これにより Apple family 9 GPUの ALUパイプラインの使用率が向上し 命令のスループットが改善される 可能性があります
ここまでをまとめます メモリ使用パターンに基づき 適切なアドレス空間を選択することで リソース使用率が向上します 16ビットデータ型を選択すると レジスタとメモリの フットプリント削減に役立ち 場合によっては Apple family 9 GPUのALU並列処理の 利用率が向上します レイトレーシングシェーダの場合も パフォーマンスを高めるには シェーダ実行時間を削減し リソース使用率を向上させることが 重要です
Metalレイトレーシングを使用して レンダリングするには まずシーンジオメトリを定義し 効率的な交差を実現にする アクセラレーション構造を構築します
交差は光線を作成するGPU関数から 実行されます このGPU関数がインターセクタオブジェクトを 作成し 交差を実行します 交差から返された結果には ピクセルをシェーディングするか さらに処理するために必要な すべての情報が含まれています
このプロセスのインターセクタ コンポーネントは Apple family 9 GPUでハードウェア アクセラレーションされます
ハードウェアインターセクタは アクセラレーション構造のトラバースや 交差関数の呼び出しのほか 交差結果に基づくトラバース状態の更新を 担います
インターセクタはMetal レイトレーシングの 基本的なAPIです 交差関数 レイペイロード 交差タグ およびインターセクタを 最適な方法で使用すると レイトレーシングのパフォーマンスが 向上します
カスタム交差関数は光線が サーフェスに当たる方法を 定義する強力な方法ですが カスタム交差関数は必要な 場合にのみ使用してください
カスタム交差関数はアルファテストなどの 機能を実装するために重要です アルファテストは この画像の鎖や葉など シーンに幾何学的な詳細を 追加するために使用されます アルファテストの実装には カスタム交差関数を使用します
カスタム交差関数内のロジックでは 光線がアクセラレーション構造を 通過するときに 交差を受け入れか拒否するかを決定します
この場合 カスタム交差関数ロジックは 最初の交差を拒否しますが
2番目の交差は不透明なサーフェスへの 交差であるため受け入れます
カスタム交差関数を使用すると 追加のロジックを シェーダの呼び出しで実行できます 必要な場合にのみ使用してください 不透明な三角形のインターセクタが 最速のパスです
カスタム交差関数が必要な場合 ハードウェアが交差関数ごとに並べ替えや グループ化を行うことに注意してください 交差関数が多くなると 一致するものを見つけて グループ化することが難しくなります
適切にグループ化できるよう 交差関数の重複は避けてください
また Metal交差関数のテーブル インデックス作成機構を利用して 関数ごとに1つのエントリを持つ 単純なテーブルを作成します
交差テストを実行する際は ハードウェア交差により 複数の光線のSIMDグループが 作成されてから 各光線が複数のプリミティブに対して 並行してテストされます
カスタム交差関数は並行して実行されるため 副作用のある操作を実行する場合は これらの関数をシリアル化する 必要があります これにはペイロードまたは 他のデバイスメモリへの メモリ書き込みが伴います 同様に 間接関数呼び出しなど 分岐を引き起こす操作も 交差関数実行の並列性を低減します
こうした操作は交差関数のできる限り あとの段階で実行し その時点まで 最大限の並列性を確保する ことをお勧めします
この例ではレイペイロードが最初に更新され 次にペイロード更新に関係のない作業が 実行されます
つまり ペイロード更新後に すべてのコードがシリアルに実行されます そうではなく 交差関数に変更を加えて ペイロード更新に関係のない作業を すべて先に実行してから ペイロードを更新することができます こうすると交差関数の 並列性を最大化できます
ハードウェアインターセクタの モデルに戻ります このフローチャートは プロセスを示していますが 重要な要素が1つ欠けています
交差処理中にレイトレーススクラッチ空間を 使用してトラバース状態を保存し 交差を呼び出すGPU関数に結果を返します
Intersector APIは各光線の ペイロードに対応します ペイロード構造体が大きくなるほど レイトレーシングのパフォーマンスに 大きな影響を与えます
レイペイロードに関しては 必要なデータのほとんどは交差結果に 含まれている可能性があるため レイペイロードの使用は 避けることをお勧めします ペイロードが必要な場合 グローバルウーバーペイロード構造体は 避けてください 代わりに 各交差呼び出しの 構造体を特殊化します
パックされたデータ型を使用して 構造体のサイズを最小限に抑え 不要なフィールドを削除します レイペイロードの使用を最適化すると より多くの光線を処理できます
例として 交差位置 ヒットを示すフラグ およびカラーを含む基本的な ペイロードを考えます メモリ上でフィールドは このように配置されます
positionメンバーは先頭にあります そのサイズと配置のため hitフラグは 先頭から16バイトの位置になります
RGBメンバーのバイトオフセットは32で 構造体全体のサイズは48バイトです
float3の値をパックされた 同等の値に変更すると 位置合わせによって失われるスペースが 少なくなります Metal Ray Tracing APIを 使用する場合 hitフラグは必要ないため削除できます 交差の結果で交差のタイプを 確認するだけで済みます これは特にシャドウやオクルージョンなどの 可視光線の場合に使いやすく パフォーマンスも向上します 同様に positionは光線の原点や方向 また結果から得られる交差距離に 基づいて計算できます
サイズをさらに小さくするには Metalシェーディング言語の パッキングメソッドを 使用して RGBカラーを 交差関数内で4バイトにパックできます
この例では RayDataPayload 構造体のサイズは 48バイトから4バイトまで減少しました このような方法を使用すると レイペイロードを最適化し レイトレーシングのパフォーマンスを 高めることができます
レイペイロードと同様に 交差タグも レイトレーシングの パフォーマンスに影響します
レイトレーシングスクラッチの 使用率に影響するもう1つの要因は インターセクタ上の交差タグです これらのタグは追加の状態として トラバース時に追跡されます
この宣言のworld_space_dataタグは オブジェクトからワールドへの行列 およびワールドからオブジェクト への行列を 光線ごとに保存する必要がある ことを意味します これにより レイトレーシング スクラッチの使用率が増加し インターセクト呼び出し時の 占有率に影響します
タグに関して注意すべき もう1つの重要な点は インターセクタとそれが 呼び出す交差関数の間で タグが一致する必要があることです
インターセクタは交差クエリよりも 優れています 交差クエリのAPIは レイトレーシングのパフォーマンスに 影響を与えるためです
ハードウェアインターセクタのモデルを 見てみると シェーディング言語の インターセクタによく適合しています
交差クエリはカスタム交差関数を 使用しないオブジェクトを定義します 交差コードは元のGPU関数で 実行されます ハードウェアインターセクタは コードが完了するまで待機してから トラバースを続行する必要があります
交差クエリの使用を選択した場合 ハードウェアには並べ替えを行う カスタム交差関数がなく 実行をグループ化できません
また GPU関数に結果を返すために より多くのレイトレーシング スクラッチメモリを使用する必要があります
交差クエリはレイ交差の代替モデルとして 他のシェーディング言語からの移植性を サポートするものです インターセクタはハードウェア実装と 協調がとれるため 交差クエリよりもインターセクタを お勧めします
交差クエリを使用する必要がある場合は 使用するクエリオブジェクトを できる限り少なくしてください 複数の交差クエリが必要な場合は クエリオブジェクトを再利用し プロパティを変更してください こうすると 1つのクエリに対してレイ トレーシングスクラッチを再利用できます 例えば レイトレーシング作業を行う 交差クエリオブジェクトIQ1があり さらに不透明度を不透明に設定した 別のレイトレーシング作業を 行う必要がある場合 新しい交差クエリオブジェクトを 作成するのではなく 単純に交差パラメータを使用して 既存の交差クエリオブジェクトの 不透明度を不透明に再設定します
こうすることでレイトレーシング スクラッチメモリを再利用できます
複数の交差クエリを使用する場合 クエリ間の切り替えや トラバースの重複を避けてください これにより 進行中の ハードウェアトラバース間で コストの高いスワップが 発生するのを回避できます
例えば このレイトレーシング作業で IQ1からIQ2に切り替え さらにIQ1に戻る代わりに IQ1を継続してレイトレーシング 作業を完了してから IQ2に切り替えます レイトレーシングのベスト プラクティスをまとめます 必要な場合にのみ カスタム交差関数を使用します
レイペイロードを最適化します
交差タグの数を最小限に抑えます
交差クエリよりもインターセクタを 使用します
Metalを使用したレイ トレーシングの詳細については 「Metalレイトレーシングのガイド」を ご覧ください XcodeのMetalデバッガの新しい レイトレーシングカウンタを 使用する方法については 「M3とA17 Proの新しいMetal プロファイリングツール」を ご覧ください
以上のまとめです Metalシェーダのパフォーマンスを 向上させるために 関数定数や関数グループなどの Metal機能を使用して シェーダの実行時間を短縮できます このような機能を使用すると Metalでの最適化の可能性が高まり リソース使用率とスレッド占有率が改善され 並列性が向上します
交差関数 レイペイロード 交差タグ およびインターセクタの ベストプラクティスを適用すると ハードウェアアクセラレーションによる レイトレーシングを 最大限に活用できます
ご視聴ありがとうございました
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。