HOME

Maneki Pipeline

ゲーム開発のためのプロシージャル

コンパイルブロック

内部に含むノードのメモリアクセス抑制や並列化をさせることで主に処理の高速化に利用されるノード。

ノード概要

コンパイルブロックノード

コンパイルブロックで囲んだ内部はいくつかの挙動を制限するかわりに高速化とfor each並列処理の利用ができる。

Houdini Reference | Compiled Blocks
https://www.sidefx.com/docs/houdini/model/compile.html

高速化、並列処理

高速化はメモリの読み書きの頻度が減ることで巨大なジオメトリを扱う際に早くなる(らしい)。
また、このノードが最も恩恵が得られるのはループの並行処理でFor Eachブロックに専用の設定項目がある。

For Eachの並列処理

制約がある

コンパイルできるノードには以下のような制約がある。

  • コンパイル可能なノードのみ対応
  • stamp() エクスプレッション非対応
  • ローカル変数とコンポーネント毎のエクスプレッション非対応
  • 名前による内側ジオメトリ参照に非対応
  • エクスプレッションの直接入力参照非対応
  • For EachのStop Condition非対応

個別の説明は割愛するが特に「名前による内側ジオメトリ参照に非対応」「エクスプレッションの直接入力参照非対応」はコンパイルの特徴として大きい(後述)

補足:検証用のノードを作成

今回、速度周りの検証を行ったがHoudiniで実行速度を検証する際はボタンに関連ノードのクックを行うコマンドを設定しておくと楽。 記事では以下のようなノードを用意して利用している。

  1. 検証したいノードのEdit Parameter Interfaceを開く
    速度測定用のsubnetのParameterを設定
  2. Buttonを追加しCallbackScriptに一つ前のノードを強制的にクックするコマンドを設定 [画像2] hou.pwd().input(0).cook(force=True);
    CallBackスクリプトを呼び出すボタンを追加
  3. Window/ PerformanceMoniterからswitchでコンパイルを行う場合と行わない場合で切り替えて測定する。
    コンパイルしているブロックとコンパイルしていないブロックをSwitchで切り替え
    ボタンで前のノードを強制的にcookして測定する

覚えておくべき挙動

並列処理による高速化

コンパイルブロックは処理を高速化させることに利用できるが、具体的にはfor eachのような反復処理をマルチスレッドで処理を行うことができるようになるというものである。

例えば以下のような、ただ重い処理にコンパイルブロックを設定しても速度は変わらない。

特定の処理をコンパイルブロックで挟んだのみのもの
コンパイル無し
コンパイル有り 挟んだだけでは速度はほぼ変わらない

一方でSphereごとにfor eachで処理を行うノードに対しては速度の向上が見込める。
mountain、voronoi、uv layoutをまとめた処理をfor eachで回した場合、半分以下の時間で処理が完了した。
以下の例では 23.478s → 8.441sと大きく時間が減っている。何度か試したが自分の環境であれば概ね50%~40%程度減少することが確認できた。

For Eachをコンパイルブロックで挟んだ構成
コンパイル無し
コンパイル有り 並列処理によって大きく差が出る

補足:個別のノードでテスト

個別で試してもForEachの並列処理はしっかり削減効果が認められた。

ノードごとに測定 個別でも効果が感じられた

ノード

コンパイル無し

コンパイル有り

voronoi、uv layout、

23.478s

8.441s

voronoi

6.770s

4.709s

uv layout

1.453s

0.702s

mountain

0.224s

0.069s

コンパイル可否ノード

コンパイルはすべてのノードでできるわけではなく、可能なノードはデフォルトで表示されていないバッジで確認できる。

コンパイルに対応していないノードを確認するためのバッジ

コンパイル不可なノードをコンパイルブロックに組み込むとエラーで止まる

metaball、bulgeなどはコンパイルに対応していない

ノード参照関連の制約

前述のように参照周りで制限がいくつか存在し、特にノードを関連させる機能周りの制限が大きい。

名前による内側ジオメトリ参照に非対応

名前でのノード参照ができない

名前で参照しているExpressionを含んでいるとコンパイルエラーとなる

エクスプレッションの直接入力参照非対応

文字列だけでなく数字で行うような参照もできなくなる。

数字で入力を参照したエクスプレッションもエラーになる

ノード参照するには

代替としてSpare Inputによる参照にするとコンパイルブロックの前に参照されると明示できるらしく、ノード参照をしたい場合はSpareInputによる参照に置き換える必要がある

Spare Inputを追加すればノード参照を利用できる
-1(-2,-3...)番目の入力がSpareInputとなる Spare Inputは明示的にコンパイルの事前処理となりコンパイルブロック内で利用できる

ノード参照ができない結果、それをしているHDAも利用できない

前述のように制約を内部で行っていないノードはコンパイルができない。
参照などは当然のように利用機能であるため、Labsノードなども基本的にコンパイルブロックに利用できない。

ノード内の参照でも制約に引っかかる場合はコンパイルできないため、Labsなどもそのままでは多くがコンパイルに含むことができない

※気にしすぎない方がいい

元も子もないがリファレンスのNote項目に「コンパイルブロックをどこにでも追加しようと躍起になってしまいますが、それを我慢した方が良いでしょう。 この欠点は、その恩恵がその努力に見合わないことがよくあることです。」と記載されている。
制限項目を見てもらうとわかる通りノード参照が複雑になってしまうのでコンパイル用の構成に変更するのは労力は少なくないので基本、For Eachの重い部分だけはやっておくといい、程度の認識が良さそう。

テクニック

for each の並列化

前述でも述べたがfor eachはコンパイルによる処理の並列化に対応している。 パラメータを変更しないと並列処理にしてくれないので注意(パラメータ設定しないと高速化しなかった)。
またドキュメントには複数ループがある場合は分散タスクが増えすぎないように最上位ループのみでこの設定を有効化することが推奨とされている。

For Eachブロックにはコンパイルを並列処理する専用のパラメータがある

ブロックの複数入力

Compileブロックに複数のノード入力をしたい場合、Compile Beginのみを追加し、2つ目のCompile BeginのBlock PathをBlock Endに指定すると一つのブロックとして認識してくれる。

Block Beginを追加し、Block Endに紐づけると同じブロックの他の入力として扱える

invokeを使ったコンパイルブロックの再利用

invokeノードと組み合わせればシーン内のみのローカルなHDAのように扱える。
使い回したいCompile Blockに入力名を指定する項目があるので設定し、

Invokeで呼び出したいコンパイルブロックの入力名を定義

InvokeでCompile Block Endの指定とその入力名を設定すると同様の動作をするノードとして扱える。

Invokeノードでコンパイルブロックと入力名を指定すると同様の処理を呼び出せる

シーンだけ渡すときとかのHDA化面倒なときとかに使うといいかも。
(個人的にはコンパイルの制約管理がちょっと大変なので使い回すならHDAの方が良い気がしている)


コンパイルブロックで早くなることは聞いたことがあったのですが、どのくらいの効果があるか知らなかったのでまとめてみました。
重い処理では劇的な効果が認められそうなので仕様周りについて調べてよかったです。

AI時代が来ると大量処理の需要が今以上に増えそうですしね。

fish_ball

プロシージャル魚類