TouchDesigner のパフォーマンスチューニング & TIPS

TouchDesignerのパフォーマンスチューニングの時に気をつけることと、今まで触ってみて気付いたTIPSをつらつら書いていこうとおもいます。

サンプルファイルは **[こちら](https://drive.google.com/open?id=1H9oP21_PlExu661R5Hg8P3MJinBOfDoU)**

## インストールしたら最初にやる事

### `Adaptive Homing by Default`をオフにする
メニューバーの、 `Edit > Preference` から、`Geometry` タブの中にある `Adaptive Homing by Default` をオフにします。

![homing_setting.png](https://qiita-image-store.s3.amazonaws.com/0/38051/a323e666-f85a-5bc7-149b-7060be33af90.png)

これが入っているとSOPのプレビュー領域が常にジオメトリがカメラの範囲内に入るように計算してくれるようになりますが、裏を返せば毎フレーム全ポイントをなめていってジオメトリの中心と大きさを算出する、というものなので、ポイント数が増えてくるとプレビューだけで相当な重さになります。

そんなにジオメトリを眺めていい事もないと思うので**絶対に**デフォルトで切るようにしましよう。

どうしても使いたい場合はSOPを選択してViewer Avtiveにした後に右クリックメニューから有効化することができます

![homing.png](https://qiita-image-store.s3.amazonaws.com/0/38051/4e9b0b76-40b6-2e1d-3ae9-8048ed299b47.png)

### バージョン管理は他でやる事が多いのでバックアップは作らない
好みの問題になりますが、個人的にはバックアップファイルでプロジェクトのフォルダが荒れるのが嫌だな～… とおもうので思い切ってバックアップは作らず、git、svnなどでバージョン管理をしています。

`Preference > General > Incremental Filename on Save` で `Off` で切ります。

## `CPU Cook Time`をこまめに気にする
オペレーターを中クリックすると`CPU Cook Time` 又は `Children CPU Cook Time`が出るので、ちょいちょい気にしながら確認するようにしましょう。

![cpu_cook_time.png](https://qiita-image-store.s3.amazonaws.com/0/38051/518f4710-b54f-9b04-75a2-b144896ac603.png)

指標としては1フレームが16msで終わればいいので、単純計算で1msかかっている処理であれば16個まで増やせる、ということにまります。

無闇矢鱈と最適化に走っても逆に生産性が落ちてしまうので、処理時間とシーン内での処理の重要性を意識することが大事です

逆に言えば処理が重くても、ものすごいキーになるような計算なのであれば、プロジェクトのフレームレートを30fpsとか24fpsに設定して成立すればOKな訳です。**大切なのは60fpsをキープする事ではなくて、いい感じの絵を出す事です！**

プロジェクト全体のパフォーマンスを確認するには [`Performance Monitor Dialog`](https://www.derivative.ca/wiki088/index.php?title=Performance_Monitor_Dialog) も参考になります

## いらないGeometry系のプレビューを切る
色々な所で内容が表示されると便利なのですが重くなる原因にもなります。

必要ない所では、Preview、Display、Render、Templateのフラグを切っておきましょう。

![render_flags.png](https://qiita-image-store.s3.amazonaws.com/0/38051/473bc204-0586-a208-212b-82fb42eccfa5.png)

個人的にはTOP、ある程度のサンプル数までのCHOPは表示しててもそこまで重くならないと思っているので放置しています

## 解析系のTOPは解像度が低くても言うほど精度に差がないので落とす
`Trace SOP`など、一部の画像解析系のオペレーターは解像度を上げているとその分処理が重くなる時があります。

処理が重いなと感じたら入力の解像度を落として出力にどの程度の影響が出るかを見極めるようにしましょう。(大抵のOpenCV系の処理は解像度落としてもそんなに影響ないです)

クオリティの低下が許容内であれば軽い方がいいですからね…

## Blur TOPの`Pre-Shrink`は見た目に問題がないレベルまで下げる
TouchDesignerだとブラーも簡単にかけれるので多様してしまいがちですが、GPU負荷の高い計算である事に変りはありません。

`Filter Size` の数値を上げるとボケの半径が大きくなりますが、その分処理負荷が高くなります。その時に`Pre-Shrink` の数値を上げると1/N倍にダウンサンプリング → ブラー処理 → N倍にアップサンプリングというような処理をしてくれるので、結果的にブラー処理のピクセル数が削減でき、パフォーマンスが上がります。

絵的には `Filter Size` でボカした方が綺麗なのですが、そこまで綺麗にぼかす必要がないのであれば可能な限り `Pre-Shrink` のほうでボカすようにしましょう

ちなみにExprementalのほうだとオペレーターを中クリックで `GPU Cook Time` も表示されるようになっているので、GPU側の最適化もやりやすくなっています :thumbsup:

## 数が必要な場合、`Copy SOP`ではなくてGeomerry Instanceを使う
`/project1/COPY_SOP_vs_GEOMETRY_INSTANCE`を参照

個人の感想ですが、TouchDesignerだとオブジェクトの数を出そうとするとSOPで対応するのは結構キビシイなと感じでいます…

100個の球体を複製という処理を`Copy SOP`とジオメトリインスタンスを使った方法と比較するとパフォーマンスに60倍の開きがありました 

表現の幅もジオメトリインスタンスを使ったほうが広い事が多いので、`Copy SOP`の出番は見付ける方が難しいのではないかと思います…

## Script CHOPとConstant CHOPの使いわけ
`/project1/SCRIPT_CHOP_vs_CONSTANT_CHOP` を参照

CHOPの値に簡単なエクスプレッションを書き込みたい、とか、他のCHOPを参照した値を取り出したいといった時に`Script CHOP`と`Constant CHOP`のどちらを使ったらいいか？というものです。

個人的には扱うCHOPのデータが1サンプルの時にはどちらでもいいのかなと思いました。軽く検証してみましたが、1サンプル程度のデータであればパフォーマンスはどちらも変わりありませんでした。

ただ、マルチサンプルのデータを扱うとなると`Constant CHOP`では処理できないので`Script CHOP`になると思います。

似たようなものとして`Expression CHOP`がありますが、僕は全然使ってないです。

## Pythonのあれこれ

### op(NAME) や op(NAME)[CHANNAME] の文字列ルックアップは地味に重いので可能であればキャッシュする
`/project1/CACHE_CHANNEL_OBJECT` を参照

CHOPのオブジェクトはPythonから、チャンネルインデックス、又はチャンネル名で参照できます

```python
my_chop = op('my_chop')
my_channel = my_chop[0] # index access
my_channel = my_chop['chan1'] # string access
```

ですが、簡単に計測した所、チャンネル名とインデックスのアクセスを比較するとチャンネル名のほうが6倍ぐらい遅いです

なので、必要であればルックアップしたオブジェクトをキャッシュして使います。

```python
chans = [] # グローバルレベルに適当な配列を用意する

def onCook(scriptOp):
	input = scriptOp.inputs[0]

	if not chans: # 配列が空の場合キャッシュする
		for i in range(input.numChans):
			chans.append(input['chan%i' % i]) # ここでは名前アクセスでもOK

	scriptOp.clear()

	scriptOp.numSamples = input.numChans
	scriptOp.appendChan('chan1')

	for i in range(scriptOp.numSamples):
		scriptOp[0][i] = chans[i][0] # キャッシュした配列にアクセスする

```

キャッシュした副作用として、入力のチャンネル数が変わると参照が外れてバグの原因になるので、キャッシュをクリアする仕組みを作るか、チャンネル数が変わった場合カット/ペーストで強制的にオブジェクトを作り直すか、などの対応が必要になるかと思います。注意

### cProfileでスクリプトの実行をプロファイルする
`/project1/USE_CPROFILE` を参照

Python側でやる事の規模が大きくなってくると、TDのプロファイラではPython側の処理が補足できないので、どこで時間がかかるかを見極めるのが難しくなります。

そういう時はPythonの`cProfile`モジュールを使ってプロファイルしましょう。

適当な`Text DAT` を作って以下のようなコードを右クリックから`Run Script`、または選択して`Ctrl+R`してみましょう

```python
import cProfile
pr = cProfile.Profile()

pr.enable()

for i in range(1000):
	mod('execute1').onFrameStart(None) # ここで計測したい関数を実行

pr.disable()

import pstats
stats = pstats.Stats(pr)
stats.sort_stats('tottime')

stats.print_stats()
```

上記の例だと1000回関数を実行した時にどれぐらいの時間がかかっているか、などの情報が表示されると思います。

さらにその関数の中でも、どの部分にどれぐらいの時間がかかっているか等も表示されるのでやみくもに最適化をするよりもかなり時間が節約できます :ok_hand:

## 何でもないSOPが異常に重い
`/project1/WAITING_DISPLAY_SYNC` を参照

たまに、`CPU Cook Time`を見ると何もしてないSOPが10-14msくらいかかっている時があります。

これはレンダリングがディスプレイの同期待ちでブロックしている時間だと思われるので特に気にしなくても大丈夫だと思います

## ジオメトリをまとめる

![merge_geometry.gif](https://qiita-image-store.s3.amazonaws.com/0/38051/53971515-fa46-1db1-700a-80af027cf1bf.gif)

ネットワークが同一改装で横にひろがると、表示すべきオペレーターが多くなってしまうのでパフォーマンス的にも構造的にもあまりよくありません。なので、適当な意味的なまとまりでグループを作るのはいいアイデアです

↑ のgifは右クリックから`Collapase Selected`で複数のオペレーターを`Base COMP`に入れた後に`Change COMP Type`する事で、複数のジオメトリをまとめて単一のジオメトリにする操作です。

ジオメトリをまとめると、親のほうでトランスフォームやマテリアルを変更すると子のほうも一気に適応されたりなどとても使い勝手が高いので、覚えておいて損はないテクニックだと思います

## イベントと操作を分離する
`/project1/SEPARATE_EVENT_AND_OPERATION` を参照

Execute系のDATは結構よく使いますが、たとえば`Execute DAT`で`onStart`のイベントで何かしたいと思った時にテストするのにTouchDesignerの再起動が必要になったりしてとてもめんどくさい時があります。

そういう時はイベント系のDATからは、別の`Text DAT`のスクリプトをキックするようにしましょう。

```python
op('MY_TEXT_DAT').run()
```

のような感じです。`.run()` には引数も渡せます 詳しくは[こちら](http://www.derivative.ca/wiki099/index.php?title=DAT_Class)

`Text DAT` は他のExecute系のDATに比べて簡単に編集できて、`Ctrl+R`で簡単に実行できるのでテストの効率がよく、開発時間の短縮とバグ率の低下が期待できると思うのでオススメです


## OSCを受ける時のTIPS

### `OSC In CHOP`の注意点
`/project1/DROP_OSC_FRAME` を参照

TouchDesignerではOSCを受信するのにCHOPとDATの両方がありますが、1フレームのみトリガーなどのメッセージを受ける時には`OSC In CHOP`はオススメできません

理由は、処理が重くなったりしてTD全体のFPSが下がってしまった場合にCHOPのCookの処理が走らなくなる時があるという物なのですが ↓ のgifを見てもらったほうが早いと思います

![osc_drop_frame.gif](https://qiita-image-store.s3.amazonaws.com/0/38051/797406e9-fda9-abd4-15bd-3c1d17213d04.gif)

`Hog CHOP` で仮想的にCPUリソースを食いつぶした時にどういう挙動になるかという奴なのですが、デフォルトのままの`OSC In CHOP`の設定だとPulseをとりこぼしているのがわかると思います。

`Queued` の設定を入れるとちゃんと動いているように見えますが、若干レイテンシが発生するようです (ここのあたりあまりわかっていません… レイテンシが解消されればそっちのほうが楽なので、知ってる方教えてください！)

なのでOSCの入力を使うのであれば `OSC In DAT` のほうを使うのをオススメします。こちらの場合はCookの間に入ってきたメッセージが配列として取得できるので、ユーザー側で任意を処理を入れることができます

ただ、上記の問題は1フレームのみの取りこぼすと大変な事になる系のトリガーの話なので、センサーからの連続量をとりたい等の用途であれば普通に`OSC In CHOP`で必要十分です。

そのへんはケースバイケースですが、念のため `Hog CHOP` を走らせてみてFPSが落ちてもちゃんと動作するかの確認はしておいた方がいいと思われます

**人生を左右する1パケットもありますので、取りこぼさないようにしたいですね！**

### `OSC In DAT` のメッセージをルーティングする
`/project1/ROUTE_OSC_DAT` を参照

`OSC In DAT` を普通に使っていると長々としたif - elif大会になってしまうので、外部の`Table DAT`を参照してルーティングのテーブルを作ってしまおうというアイデアです

![route_osc.png](https://qiita-image-store.s3.amazonaws.com/0/38051/595dcb02-30a0-6b25-5bb6-4c7a65a28f29.png)

さらに前述した外部`Text DAT`呼び出しのパターンを組み合わせるとさらに見通しがよくなりますね

## なるべく参照ではなく接続を使う
これは完全に好みの問題になってしまいますが、個人的には`Select`系のオペレーターを使った参照や、エクスプレッションで`op(NAME)`を使った参照を多様すると、だんだんネットワークが読みづらくなり、これ消してもいいよなと思って考え無しに消すと実は色々な所で参照されていて盛大にバグる、みたいな事が起きやすいなと思っています。

なので、参照を使う場合はできるだけ同一階層内に留めておいて、階層をまたぐ場合はそれぞれ`In`、`Out`を使って接続する、全体から参照したいグローバルなオペレーター(通信系、デバイス系) は、`/GLOBAL` みたいな階層を作ってそこに絶対に配置するようにする、といった工夫をするだけでシステムの堅牢度が上がるのではないかと思って最近はネットワークを組んでいます。

# まとめ

TouchDesignerは非常によくできた環境で、素早くクオリティの高いアウトプットができますが、ある程度ソフトウェア設計的な考え方を取り入れてネットワークを組んでいくとさらに生産性が上がるのではないかと思います。

ビジュアルプログラミング環境でのソフトウェア開発哲学のようなものは [ここ](https://vvvv.org/node/162994) に非常によくまとまっているので一読をオススメします :ok_hand:

長々とあまりまとまりのない記事になってしまいましたが、このあたりで！