5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【iOS】Metal Best Practicesの解説(2) 永続オブジェクト

Last updated at Posted at 2021-06-11

Metal Best Practicesは、iOS/MacOS/tvOSのAPIであるMetalを用いた設計のベストプラクティスガイドです。

本稿では、何回かに分けてこのガイドを読み解き、コード上での実験を交えて解説していきます。
読んでそのまま理解できそうなところは飛ばしますので、原文を読みながら原文のガイドとしてご利用下さい。

他の記事の一覧は、初回記事より参照下さい。

Persistent Objects (永続オブジェクト)

永続オブジェクトを早期に作成して再利用します。すべてのレンダリングや計算のループでこれらのオブジェクトを作成するのは非効率です。次に、オブジェクトとそれぞれの作成タイミングを示します。

Metalに関するオブジェクトと作成のタイミング一覧

オブジェクト名 作成タイミング
MTLDevice アプリ開始時にGPUごとに1つ作成する。
MTLCommandQueue アプリ開始時にGPUごとに最低1つ作成。各キューが異なる作業をする場合は、それぞれ作成する。
MTLLibrary なるべくビルド時に構築する。Xcodeはアプリのビルド時に自動的に.metalをコンパイルして、デフォルトライブラリにビルドする。newDefaultLibraryで取得できる。
MTLFunction 作成はライブラリのビルド時と同じ。複数のパイプラインで使用する場合も同じものを再利用する。
MTLRenderPipelineState, MTLComputePipelineState アプリ開始時に必要なパイプライン毎に作成する。
MTLBuffer, MTLTexture 静的データの場合は、最初に作ったものをなるべく再利用する。動的データの場合も、最初に領域を確保しておき、データを更新するようにする。トリプルバフッファリングを用いると、フレーム毎に新しいバッファを作成せずにプロセッサのアイドル時間を最小限に抑えることができる。

コードで試してみる

こちらのリポジトリにサンプルコードがあります。

サンプルコードの中に、Particleというサンプルがあるのでこれを改変しながら計測していきます。

(実行イメージ)

毎フレームごとにオブジェクトを生成する場合

効率が悪いとされるオブジェクト作成処理を毎フレームごとに入れています。

MTLDevice、MTLCommandQueue、MTLRenderPipelineState, MTLTexture, MTLBufferなどベストプラクティスで再利用せよと書かれているものをほとんど再作成してみます。実際のコードは処理時間を計測するために、前後でos_signpost関数を実行しています。

PersistentObjects1MetalView.swiftv
func draw(in view: MTKView) {
    guard let drawable = view.currentDrawable else {return}

    // wasteful processing start
    let metalDevice = MTLCreateSystemDefaultDevice()!
    let metalCommandQueue = metalDevice.makeCommandQueue()!
    buildPipeline()
    initTexture()
    makeBuffers()
    // wasteful processing end

計測してみます、

os_signpost関数で取得したパフォーマンスデータは、InstrumentsのMetal System Traceで確認できます。(Time Profilerでも確認できますが、Metal System Traceを使うとGPUの処理と並べて比較できます)

Metal System Traceの使い方はこちらの記事をご覧ください。

計測結果は次のようになりました。

image.png

フレームごとの処理は次の順番になっています。

MetalPerformance(2.44ms) ・・・ 今回計測した処理
=> Metal Application(452μs) ・・・ コマンドバッファの作成処理
=> GPU(11.21ms) ・・・ GPU側の処理(VertexシェーダーやFragmentシェーダーの処理など)
=> Built-in Display(33.33ms) ・・・ 描画

60FPSを実現するためには、16.67ms間隔で描画する必要がありますが、今回は33msかかっていました。これは、描画のためのCPU+GPUの処理が16.67msに間に合わなかったためです。このうち、今回計測した処理が2.44ms占めているのでかなりコストが大きい処理です。

内訳をみていくと次のようにっていました。所要時間の大きい順に並べています。

作成コストの内訳

オブジェクト 所要時間
MTLCommandQueue 1,100μs
MTLRenderPipelineState 887μs
MTLTexture 328μs
MTLBuffer 204μs
MTLDevice 45μs
合計 2.86ms

1つ1つは短い処理ですが、『塵が積もれば山となる』ですね。
フレームレートはだいたい30FPSぐらいになっていました。

オブジェクトを再利用した場合を試してみる

今度はオブジェクトを最初に作成したら再利用しつづけるパターンで計測してみます。
ソースコードは、Pattern2_Reuseです。

計測結果は次のようになりました。

image.png

こちらは毎フレームにオブジェクトを作成しないので、その分早くなります。ところどころ16.67msで表示できていることがわかります。フレームレートはだいたい40FPSぐらいでした。
(実はGPUの処理もだいぶ時間がかかるので、60FPSは出せない作りになっています。)

結論

ベストプラクティスにあるとおり、永続オブジェクトを早期に作成して、再利用したほうが効率的ということがわかりました。

最後に

iOSを使ったARやML、音声処理などの作品やサンプル、技術情報を発信しています。
作品ができたらTwitterで発信していきますのでフォローをお願いします🙏

Twitterは作品や記事のリンクを貼っています。
https://twitter.com/jugemjugemjugem

Qiitaは、iOS開発、とくにARや機械学習、グラフィックス処理、音声処理について発信しています。
https://qiita.com/TokyoYoshida

Noteでは、連載記事を書いています。
https://note.com/tokyoyoshida

Zennは機械学習が多めです。
https://zenn.dev/tokyoyoshida

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?