iOS 11 / macOS 10.13から、Metalデバイスで利用中のメモリサイズを返すプロパティがMTLDevice
に追加されました。
var currentAllocatedSize: Int { get }
単位はバイトです。
APIリファレンスの原文:
The current size, in bytes, of all resources allocated by this device.
正しく表現するなら、「当該デバイスで確保されている全リソースのサイズ」となります。MetalデバイスはGPUをあらわすので、GPUで確保しているリソースの合計サイズ、という解釈でいいのかなと。
実装例:
print("allocated size: \(device.currentAllocatedSize)")
実験
実験1: 画像を表示するだけのアプリの場合
まずは上記の1行コードを、(Metalを利用して)画像を表示するだけのサンプルアプリに組み込んでみました。
func draw(in view: MTKView) {
print("allocated size0: \(device.currentAllocatedSize)")
guard let drawable = view.currentDrawable else {return}
print("allocated size1: \(device.currentAllocatedSize)")
guard let commandBuffer = commandQueue.makeCommandBuffer() else {fatalError()}
print("allocated size2: \(device.currentAllocatedSize)")
guard let blitEncoder = commandBuffer.makeBlitCommandEncoder() else {fatalError()}
print("allocated size3: \(device.currentAllocatedSize)")
// コピーコマンドをエンコード(略)
...
blitEncoder.endEncoding()
print("allocated size4: \(device.currentAllocatedSize)")
commandBuffer.present(drawable)
commandBuffer.commit()
print("allocated size5: \(device.currentAllocatedSize)")
commandBuffer.waitUntilCompleted()
print("allocated size6: \(device.currentAllocatedSize)")
}
- ドローアブル取得前
- ドローアブル取得後 / コマンドバッファ作成前
- コマンドバッファ作成後 / コマンドエンコーダ作成前
- コマンドエンコーダ作成後 / コマンドエンコード前
- コマンドエンコード完了後 / コマンドバッファをコミットする前
- コマンドバッファをコミットした後
- コマンド完了後
の7箇所でメモリ使用量を調べています。
出力:
allocated size0: 16777216
allocated size1: 26935296
allocated size2: 26935296
allocated size3: 26935296
allocated size4: 27164672
allocated size5: 27164672
allocated size6: 27164672
どのタイミングでリソースが確保されるのかわかって面白いです。
それにしても、試したアプリで明示的に確保しているリソースはテクスチャ1枚(プラス、MTKViewのdrawableが持つテクスチャ)とかなので、上の出力結果を見た感じだと、16MB〜27MBというのは大きすぎるような気がしました。当該アプリが明示的にアロケートしているリソース分だけじゃなく、そのiOSデバイスのGPUが利用している量全体が得られているのかな?と。
というわけで、Metalで何もしない場合はどうか、試してみました。
実験2: 何もしないアプリの場合
Metalの処理を何もしないアプリに組み込んでみました。Xcodeテンプレートから"Single View App"を選択して下記1行をviewDidLoad
に貼り付けただけです。
print("allocated size: \(MTLCreateSystemDefaultDevice()!.currentAllocatedSize)")
出力:
allocated size: 0
ゼロでした。というわけで、やっぱり本プロパティが返す値は、当該アプリで使用しているMetalリソースの合計サイズということになります。
そう考えてみると、普段接している画像のサイズというのは、可逆圧縮フォーマットであれ、圧縮されたファイルサイズです。読み込んでいる画像の生のサイズを計算してみると、
2006 (W) * 2005 (H) * 4 (32ビット) = 16,088,120 [バイト]
となり、最初の検証で見た値は、当該アプリが使用しているリソースサイズとして非常に妥当なものであったことがわかります。
実験3: テクスチャのプロパティを変えた場合
確保するMTLTexture
の
public var usage: MTLTextureUsage { get }
や、その親プロトコルであるMTLResource
の
public func setPurgeableState(_ state: MTLPurgeableState) -> MTLPurgeableState
を使用した場合に、どう変化するのか調べてみます。が、新しい話もいくつか出てくるのでそれはまた別記事にて・・・
ちなみにMTLResource
にも
public var allocatedSize: Int { get }
がiOS 11で追加されているので、リソース確保の内訳を知りたい場合はこれを利用すると良さそうです。
活用例: "Terminated due to memory issue"によるクラッシュへの対処
Metalを利用する機能の開発中、サイズの大きい画像を処理しようとした場合や、CNNの処理で次元数が膨れ上がった場合に、次のようなエラーに遭遇することが多々あります。
Message from debugger: Terminated due to memory issue
メモリの問題で停止、と言っていますが、とはいえ、XcodeのMemory Reportで見てもCPUでのメモリ使用量しかわからないので、何の参考にもなりません。
そこで本機能を利用して、
- どれぐらいGPUのメモリ使用量が膨れ上がったところで停止したのか
- どの処理でメモリを食っているのか
といったことを調べられます。
allocated size: 159907840
allocated size: 436830208
allocated size: 856391680
allocated size: 1410334720
Message from debugger: Terminated due to memory issue
参考
- WWDC17 "Introducing Metal 2" Memory Usage Queries
- スライド p.141-
- さらっと出てきただけ。
Metalの参考書籍
技術書「iOS 11 Programming」の、「第13章 Metal」を執筆しました。
書籍のタイトルにはiOS 11とありますが、Metalについては新機能の紹介だけではなくて、基礎からじっくり解説しています。Metalの章だけで37ページもあり、日本語ではこれだけのまとまったMetalの解説はレアかと思います。
- 13.1 はじめに
- 13.2 Metalの概要
- 13.3 Metalの基礎
- 13.4 MetalKit
- 13.5 Metal入門その1 - 画像を描画する
- 13.6 Metal入門その2 - シェーダを利用する
- 13.7 Metal入門その3 - シェーダでテクスチャを描画する
- 13.8 ARKit+Metalその1 - マテリアルをMetalで描画する
- 13.9 ARKit+Metalその2 - MetalによるARKitのカスタムレンダリング
- 13.10 Metal 2
- 13.11 Metalを動作させるためのハードウェア要件
他の章も他著やネットではなかなか得られない濃い情報が詰まっているので、気になった方はぜひサンプルPDFもあるので見てみてください。
PEAKSのサイトにて電子書籍・紙の書籍ともに販売されています。
- 著者:堤 修一,吉田 悠一,池田 翔,坂田 晃一,加藤 尋樹,川邉 雄介,岸川克己,所 友太,永野 哲久,加藤 寛人,
- 発行日:2017年11月16日
- 対応フォーマット:製本版,PDF
- PEAKSで購入する
執筆を担当したARKit、Metalの章の詳細、あるいは全体的なおすすめポイントは以下の記事にも書きました。