Compileを利用したメモリの節約手法を検証します。

TEXT_秋元純一 / Junichi Akimoto(トランジスタ・スタジオ/ディレクター)
日本でも指折りのHoudini アーティスト。
手がけてきた作品は数々の賞を受賞している。
代表作に、HIDETAKETAKAYAMA『Express feat. Silla(mum)』など。
www.transistorstudio.co.jp
blog.junichiakimoto.com


EDIT_小村仁美 / Hitomi Komura(CGWORLD)

Houdiniのワークフローにおける「コスト」の考え方

今回は、Houdiniのワークフローにおける大きなテーマである、「節約」に関して検証していきたいと思います。節約へのアプローチは様々あり、どれが正しい方法かはその都度変わるもので、正解はありません。ただ、様々なアプローチができるように、引き出しは多いほうが良いと考えます。

昨今、マシンパワーの向上により、あまりメモリやストレージ、CPUなどの負荷を考えない傾向にあると感じます。特に、近年のHoudiniは一昔前に比べて非常に高速化していることも相まって、ワークフローにおける節約の頻度は確実に減っている、もしくはなくなってしまっていると思います。巨大なデータを扱えるようになったため、それがまるで当たり前のように振る舞ってしまうことで、見えないところで無駄な負荷をインフラに与えてしまっている傾向があります。

Houdiniは元々非常に高コストなソフトであり、先人たちは知恵を絞ってそのコストを落としてきました。汎用的なHDAなどがあることは、その知恵を探る機会を奪っているとも言えます。今回は、そんな状況を打破する意味も込めて、Houdiniのワークフローにおける節約と言うテーマから、Compile SOPを用いたアプローチに絞って検証していきたいと思います。

今回のHoudiniプロジェクトデータはこちら

01 Compile test:case 01

Compileの検証をしていきます。

まずは、Houdiniに負荷をかける準備をします。Forを用いて、単純なBoxに対し、SubdivideとDeformをくり返し適用します【A】

このとき、Block Begin SOP【B】はMethodをFetch Feedback【1】に設定し、Block End SOP【C】のIteration MethodはBy Count、Gather MethodはFeedback Each Iterationに設定します【2】。この設定で得られる結果は、前のIteration結果をFeedbackしてそれぞれのIterationにおいてSubdivideおよびDeformを適用するというものです【3】。その結果、全体のフローで【4】のような結果となりました。



次に、Compile SOPを用いたフローで試します。Compileの用い方はForと同様で、処理したいフローをBlock Begin Compile SOP【D】とBlock End Compile SOP【E】で挟み込む手法になります。

その結果、下図【5】のようになりました。ここから見えてくる情報を読み解くと、Compileを使用しないアプローチでは、メモリの消費が非常に多いことがわかります。その代わり、Compileの方が、若干Cookingに時間がかかっている状況です。この原因はHoudiniのCookingのしくみにあり、SubdivideやDeformなどの処理をするためにいったんジオメトリの情報をキャッシュしているため、Iterationが増えると同時にメモリ消費が上がるためです。しかし、Compileを使用した場合、キャッシュの受け渡しが最低限になるため、このようにメモリ消費を最小限に抑えることができます。

また、Compileを使用する際には、エクスプレッションに制約ができるため、Metadata【F】を活用するためにはSpare Input【6】を使用する必要があります。Add Spare Input【7】で追加して、Metadataへアクセスし、detail()などの"../repeat_begin_metadata"を-1にして再定義する方法があります。


次ページ:
02 Compile test:case 02

[[SplitPage]]

02 Compile test:case 02

前項とはセッティングを変えて検証していきます。この検証では、Compileがもつ本来の性能を発揮するためのセッティングにしています。まず、前項ではFeedback Each IterationでそれぞれのIterationをFeedbackしてForを演算していたため、並列処理はできませんでした。Compileは本来並列処理に特化した機能なので、非常にもったいない使用法と言えます。ただ、メモリに関してはかなり消費を抑えられているため、それもまたひとつのアプローチといって良いでしょう。

ここでは、Copy to Points SOP【A】を用いて、PointにSubdivideとDeform【B】をかけたジオメトリをコピーしますが【1】、このとき、あえてPointごとに処理をします。あまりこういう機会はありませんが、性能を引き出すために、意図的に処理の負荷を上げるようにします。このとき、Block Begin SOPを2つ用意し、どちらがBlock End SOP【C】のIterationにつながるかを設定します。今回はPointそれぞれに対して処理をするため、Iteration MethodをBy Pieces or Pointsに、Gather MethodをMerge Each Iterationに設定します【2】

Point側のBlock Begin SOP【D】ではMethodをFetch Piece or Point【3】に設定し、コピーする側【E】ではFetch Input【4】に設定します。その結果が下図の【5】となります。

続いて、Compileを設定する際に、Block Begin Compile SOPをForに合わせて2つ用意します【F】。また、このとき並列処理を可能とするため、Block End SOPのMultithread when Compiledにチェックを入れてアクティブにします【6】

その結果が下図【7】となります。全体のメモリ使用が若干上がっていますが、Cookingの処理速度が大幅に向上しているのがわかります。このように、それぞれのIterationを並列処理できるシチュエーションでは、かなりの速度向上が見られました。


次ページ:
03 Invoke

[[SplitPage]]

03 Invoke

ここでは、Compileの応用として、Invoke Compile Block SOPを使用していきたいと思います。少し変わった性質をもつこのオペレータですが、簡単に言えば、指定したCompileのフローを、そのまま自身に置き換えて演算すると言うものです。これは、Compileのフローでいったんコンパイルされた情報を再利用しているということになります。そのため、同じフローであれば、HDAなどを定義せずとも、より効率的に同様の処理を1つのノードで行うことができます。

まず、Compile Blockを指定し、次にBlock Begin Compile SOPでInput Nameを指定して、そのNameをInvoke Compile BlockのInput Nameと一致させます。これにより、Invokeで再度インプットを指定しても、同じながれを維持することができます。これにより受けられる恩恵は、計り知れないものになります。まず、同様の処理を行う際に、これまではHDAなどの定義により効率化を図っていましたが、その都度パラメータの調整が不要な場合などは、煩わしい作業でもあります。それを一手に担ってくれるアプローチが増えたということは非常にありがたいもので、今後の発展に期待がもてます。

次ページ:
04 Operators

[[SplitPage]]

04 Operators

主要ノードを解説します。

Compile SOP

Compile Blockは、Block Begin Compile【A】とBlock End Compile【B】の2つが連動して動作します。それぞれ単独で使用することはほとんどありません。これらの内部がコンパイルされた状態となり、メモリの節約や、Multithreadなどを使って処理能力を向上させます。Block Begin Compileノードに接続され入力だけをCookingする状態で、Block内全てのノードを各々で処理しないため、無駄なキャッシュを省けます。また、Forと併用すれば並列処理も可能な点が、最も特筆すべきポイントになります。Unload Behaviour【1】では、中間データのアンロードに関する設定ができます。デフォルトでは、Alwaysになっており、キャッシュ状態を保存する設定となっています。Force Compile【2】では強制的に再コンパイルします。これはコンパイルエラーなどが起きた際、再度生成する際に重宝します。

Invoke Compile Blockは、コンパイルされているBlockを指定して【3】、 それを再度呼び出すことができるオペレータです。Invokeは「呼び出す」や「召喚する」という意味があります。Block Begin Compile にはInput Name【4】が追加され、Invoke Compile Block のInput Name【5】と一致させることで、指定したBlockと同等の処理を行えます。これにより、HDAを定義しなくても、Compile Blockの処理と同等の結果を得ることができます。

最後にCompileを使用する際に注意しなければならないのが、全てのオペレータが対応している訳ではないと言う点です。Network View Display Optionから、Non-Compilable SOPBadge【6】を表示すると、例えばCopy Stamp SOPのように対応していないオペレータが確認できます【7】。またその他にも、ローカル変数が使えないことや、ジオメトリの参照などにも制限があります。それらを踏まえて、正しい使用方法を身に着けましょう。