UE4 BPとC++のパフォーマンス比較検証
今年のUnreal Fest East 2019では様々なセッションがありましたが、その中でもよく話題になっていた以下のセッション。
途中でブループリントとC++のパフォーマンスについての話があります。ブループリントの速度がC++に比べても遅いのは当然ですが、さすがにいくらなんでも差がありすぎるだろうと思って、実際に比較検証をしてみました。
『ForLoopがマクロなのでハンデになっている』という説明があるので、BPが極力不利にならないようにその部分は意地でもマクロを使わずに実装を行います。
なるべく計算量が多くなるようなアルゴリズムを採用し、わかりやすい結果がでるように最も有名なソートアルゴリズムのひとつ、"バブルソート"をC++とBP両方でやります。実際のコードとノードは以下に載せていますので、参考に。
バブルソート参考
C++側のコード
以下がC++側のコードです。SortArrayには事前に要素数だけ指定し、配列要素を追加しています。
// C++でバブルソート void ASortActor::CppBubbleSort() { int32 Num = SortArray.Num(); for (int32 i = 0; i < Num; i++) { for (int32 j = Num - 1; j > i; j--) { if (SortArray[j] > SortArray[j - 1]) { SortArray.Swap(j, j -1); } } } } // 配列に要素を追加し、インデックス値をそのまま代入 void ASortActor::CppAddArrayElement(int32 SumElement) { SortArray.Empty(); for (int32 i = 0; i < SumElement; i++) { SortArray.Add(i); } }
BP側のノード
BPノードを共有するためには" BlueprintUE "というサイトを使います。
以下はバブルソート用の関数ノードです。
次は配列に要素を追加し、インデックス値をそのまま代入する関数ノードです。
Rerouteノードが少々わかりにくいですが、一度もマクロを利用せずにループが実装できていることがわかると思います。
では要素が揃ったので早速検証してみます。
検証について
検証部分は以下のように、C++側とBP側を個別に呼び出せるようにし、NowノードでBeforeとAfterをそれぞれ計測し、その間に該当の関数を配置してその差分時間を表示します。
そしてその結果をウィジェットで表示できるようにしています。
ここでひとつ大きなことがわかりますが、BPはPIE(Play In Editor)での実行とスタンドアローン実行では実行速度が上記画像からもわかるように何倍も違うので今回は計測の除外としています。
今回計測するのは以下の6つです。
・Development BP
・Development C++
・Development BP Nativize
・Shipping BP
・Shipping C++
・Shipping BP Nativize
NativizeはブループリントをC++コードに変換した結果のものです。同じBPから生成したものを表示します。
計測結果
というわけで早速計測結果です。今回はそれぞれ全て10回ずつ計測し、配列要素数は 10000 個で固定しました。結果は以下。
時間の数値は全てミリ秒となっています。
数値からわかるのは、BP自体はDevelopmentとShippingで速度差がでないということです。それに対して、C++とBP Nativizeは大きく数値が下がります。結果的にForLoopなどのマクロを使わない結果、C++とBPの速度差は最初の記事内容ほどの差はつきませんでしたが、それでもShipping時に約336倍の速度差となりましたので、依然としてBPはやはりループ処理に弱いということがわかります。
が、NativizeされたBPはC++との速度差は3~4倍程度に収まっています。この速度差であればスクリプトとして考えると十分実用的であると言えるでしょう。ひとつだけBP側にハンデがあったのは、UE4.23時点でBPの配列変数で利用可能なSwapノードをNativizeで変換しようとすると、コードジェネレーター側で定義されていないというバグがあったため、地道に手動スワップを行うために、本来1ステップで完了するところが3ステップを必要とするようになってしまったため、BP側はNativize化の際にもう少し速くなったと思います。
が、今回の計測で十分結果は出せましたので、やはりループ部分はBPよりもC++で書くかカスタムループを作ることで、負荷は大きく軽減できると思います。もしくはBP Nativizeだけでも十分な高速化を得ることができます。
どうしても最速が欲しいという部分だけ、C++で書き直すのが恐らく最も良い選択となるのではないでしょうか。
追記
ケースバイケースですが、場合によってはC++にBP Nativizeの結果の方が速くなるというケースも存在するようです。
C++で適当に複雑な計算を書き、BPでそれを数学式ノードに直してShippingで実行。ループは10万回。3枚目はNativizeなしで2~3倍の速度差。4枚目はNativizeありでなんとC++側の方が遅くなる。おそらく数学式ノードが自動的に計算を最適化しているのが原因。BPを使うとこういう結果もある。#UE4 #UE4Study pic.twitter.com/hbOw080wu9
— alwei (@aizen76) October 29, 2019
と、思いましたが、ちゃんと数学関数をUE4標準のものに見直すと、ほぼ同じ結果が出てくるようです。C++の方が遅いということはありませんが、逆に考えるとBP NativizeでC++と同等の速度を出せることも証明ができたということです。
うえしたさんからの指摘で、標準C言語数学関数ではなく、UE4標準数学関数(FMath)に差し替えたところ、何度かやってみたら多少の誤差はありますが、C++とBP Nativizeの差がほとんどなくなりました。結論としては複雑な計算でもBPのネイティブ化により、ほぼ差がないケースもあります。#UE4 #UE4Study pic.twitter.com/bOnD4TPgXw
— alwei (@aizen76) October 29, 2019