Metal / GPUで利用中のメモリサイズを調べる

iOS 11 / macOS 10.13から、**Metalデバイスで利用中のメモリサイズを返す**プロパティが`MTLDevice`に追加されました。

```swift
var currentAllocatedSize: Int { get }
```

単位は**バイト**です。

APIリファレンスの原文：

> The current size, **in bytes**, of all resources allocated by this device.

正しく表現するなら、「当該デバイスで確保されている全リソースのサイズ」となります。[MetalデバイスはGPUをあらわす](https://developer.apple.com/documentation/metal/mtldevice)ので、**GPUで確保しているリソースの合計サイズ**、という解釈でいいのかなと。



実装例：

```swift
print("allocated size: \(device.currentAllocatedSize)")
```


## 実験

### 実験1: 画像を表示するだけのアプリの場合

まずは上記の1行コードを、[（Metalを利用して）画像を表示するだけのサンプルアプリ](https://github.com/peaks-cc/iOS11samplecode/tree/master/chapter_13/01_MetalImageRender)に組み込んでみました。

```swift
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)")
}
```

0. ドローアブル取得前
1. ドローアブル取得後 / コマンドバッファ作成前
2. コマンドバッファ作成後 / コマンドエンコーダ作成前
3. コマンドエンコーダ作成後 / コマンドエンコード前
4. コマンドエンコード完了後 / コマンドバッファをコミットする前
5. コマンドバッファをコミットした後
6. コマンド完了後

の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`に貼り付けただけです。

```swift
print("allocated size: \(MTLCreateSystemDefaultDevice()!.currentAllocatedSize)")
```


出力：

```
allocated size: 0
```

ゼロでした。というわけで、やっぱり本プロパティが返す値は、**当該アプリで使用しているMetalリソースの合計サイズ**ということになります。

そう考えてみると、普段接している画像のサイズというのは、可逆圧縮フォーマットであれ、圧縮されたファイルサイズです。読み込んでいる画像の生のサイズを計算してみると、

2006 (W) * 2005 (H) * 4 (32ビット) = 16,088,120 [バイト]

となり、最初の検証で見た値は、当該アプリが使用しているリソースサイズとして非常に妥当なものであったことがわかります。


### 実験3: テクスチャのプロパティを変えた場合

確保する`MTLTexture`の

```swift
public var usage: MTLTextureUsage { get }
```

や、その親プロトコルである`MTLResource`の

```swift
public func setPurgeableState(_ state: MTLPurgeableState) -> MTLPurgeableState
```

を使用した場合に、どう変化するのか調べてみます。が、新しい話もいくつか出てくるのでそれはまた別記事にて・・・

ちなみに`MTLResource`にも

```swift
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](http://peaks.cc/shu223/iOS11)」の、「第13章 Metal」を執筆しました。

書籍のタイトルにはiOS 11とありますが、Metalについては新機能の紹介だけではなくて、**基礎からじっくり解説**しています。Metalの章だけで**37ページ**もあり、日本語ではこれだけのまとまったMetalの解説はレアかと思います。

- 13.1	はじめに
- 13.2	Metalの概要
- 13.3	Metalの基礎
- 13.4	MetalKit
- 13.5	Metal入門その１ - 画像を描画する
- 13.6	Metal入門その２ - シェーダを利用する
- 13.7	Metal入門その３ - シェーダでテクスチャを描画する
- 13.8	ARKit+Metalその１ - マテリアルをMetalで描画する
- 13.9	ARKit+Metalその２ - MetalによるARKitのカスタムレンダリング
- 13.10	Metal 2
- 13.11	Metalを動作させるためのハードウェア要件

他の章も他著やネットではなかなか得られない濃い情報が詰まっているので、気になった方はぜひ[サンプルPDF](http://peaks.cc/shu223/iOS11)もあるので見てみてください。

PEAKSのサイトにて電子書籍・紙の書籍ともに販売されています。

<div class="peaks_widget" style="overflow:hidden; padding:20px; border:1px solid #dedede;"><div class="peaks_widget__image" style="float:left; margin-right:15px; line-height:0;"><a target="_blank" id="purchase" href="https://peaks.cc/shu223/iOS11"><img alt="iOS 11 Programming" style="border:none; max-width:140px;" src="https://s3-ap-northeast-1.amazonaws.com/peaks-images/project002_cover.jpg" width="500"></a></div><div class="peaks_widget__info"><p style="margin:0 0 3px 0; font-size:110%; font-weight:bold;"><a target="_blank" id="purchase" href="http://peaks.cc/shu223/iOS11">iOS 11 Programming</a></p><ul style="margin:0; padding:0;"><li style="font-size:90%; list-style:none;"><span>著者：</span><span>堤 修一,</span><span>吉田 悠一,</span><span>池田 翔,</span><span>坂田 晃一,</span><span>加藤 尋樹,</span><span>川邉 雄介,</span><span>岸川克己,</span><span>所 友太,</span><span>永野 哲久,</span><span>加藤&#160;寛人,</span></li><li style="font-size:90%; list-style:none;">発行日：2017年11月16日</li><li style="font-size:90%; list-style:none;">対応フォーマット：製本版,PDF</li><li style="font-size:90%; list-style:none;"><a target="_blank" id="purchase" style="text-decoration:underline; color:#1DA1F2;" href="http://peaks.cc/shu223/iOS11">PEAKSで購入する</a></li></ul></div></div>

執筆を担当したARKit、Metalの章の詳細、あるいは全体的なおすすめポイントは以下の記事にも書きました。

- [「iOS 11 Programming」を共著で執筆しました／本書のおすすめポイント - Over&Out その後](http://d.hatena.ne.jp/shu223/20171128/1511827476)