入れ子(ネスト)になったプレハブのサイズについて

f:id:hirasho0:20191125121652p:plain

こんにちは。技術部平山です。

入れ子になったプレハブは、ビルド時に入れ子が全部展開されてるっぽい、というのがこの記事の結論です。 あと追加でParticleSystemを使うとデータがデカいということもお伝えしておこうと思います。

なお、2018.4.6及び2018.4.11での話で、他でも同じかは確認しておりません。

何の話?

とあるステージクリア型のゲーム製品がありまして、各ステージをプレハブとして作っています。

前もってステージの構成要素のプレハブがたくさん用意してあって、 それをゲームデザイナがUnityEditor上で配置してステージのプレハブを作る、 という作業設計です。

Unity的には普通の設計だと思うのですが、 いざビルドしてみると、このステージのプレハブが妙にデカいのです。 それこそロード時間が気になるほどに。

一体何が起こってるんだ?という話になって調べた結果、 最初に「結果」として書いたことがわかったわけです。

アセットとしてのプレハブと、ビルド後のプレハブ

アセットとしてのプレハブのサイズは、入れ子にしても大きくなりません。

例えば、ステージに、レンガが多数置いてあったとしても、 ステージの.prefabファイルはあまり大きくなりません。 もちろんレンガの個数に応じて座標情報などは足されますが、 それだけのことです。 そして、レンガのプレハブにコンポーネントを足したとしても、 ステージのプレハブのサイズは変わりません。

考えてみれば当然です。ステージのプレハブは、レンガのプレハブを参照しているだけで、 中身をコピーして持っているわけではないからです。 子を変更しても、親を触らずに済む方が良いに決まっています (もっとも現状子をいじると、親プレハブ全部に再インポートが走ります。何か事情があるようです)。

しかし、現実にはビルドすると実際のGameObject数に比例してサイズが大きくなるように見えます。 とすれば、考えられるのは、「ビルド時に子を全部展開している(コピーしている)」ことでしょう。

本当にそうか実験してみました。

実験

Githubに実験プロジェクトを置いておきました。 やっていることは簡単です。

まず、0という名前のプレハブにキューブを一個置きます。特に意味はありませんが、 回転するRotationというコンポーネントも足しておきました。

次に、1というプレハブを作って、そこに0を2個ずらして配置します。

さらに、2というプレハブを作って、そこに1を2個ずらして配置します。 3に2を2個、4に3を2個、5に4を2個、という具合に、13まで作りました。 例えば7の構造はこんな感じです。

f:id:hirasho0:20191125121648p:plain

0にはキューブが1個、1にはキューブが2個、2にはキューブが4個、 という具合にキューブの総数は倍々で増えていきます。 k番が持つキューブの総数は2^k個で、13では8192個ですね。

あとはこいつのロード時間やInstantiate時間を測定し、 ビルドでも同じことをしてみます。 ビルドした時にはビルド後サイズも見てみましょう。

結果

エディタでの測定

プレハブ ロード Instantiate .prefabファイルサイズ
0 0.994 1.53 4kb
1 2.66 0.342 7kb
2 2.01 0.556 7kb
3 3.12 3.87 7kb
4 3.88 1.84 7kb
5 17.3 2.25 7kb
6 11.2 4.66 7kb
7 21.2 5.89 7kb
8 33.0 9.57 7kb
9 73.8 24.0 7kb
10 162 45.0 7kb
11 311 118 7kb
12 607 268 7kb
13 1260 577 7kb

ロードはResources.Loadの所要時間(秒)です。 System.DateTime.Nowの差分で測定しました。

7くらいまでは誤差が大きくてよくわかりませんが、 8からは倍々の感じで増えていますね。

そして、.prefabファイルの大きさはみんな同じです。 0だけ違うのは、こいつにだけMeshRendererが入ってて、 GameObjectの数が他と違うからですね。

ビルドでの測定

プレハブ ロード Instantiate ビルドでEditor.logに出てくるサイズ
0 49.4 2.59 0.5kb
1 15.0 0.183 1.1kb
2 6.89 0.118 2.3kb
3 9.03 0.255 4.9kb
4 3.49 0.506 9.8kb
5 11.8 0.779 19.7kb
6 14.9 1.74 39.4kb
7 13.4 3.21 79.0kb
8 30.3 4.18 158kb
9 49.9 14.6 316kb
10 109 22.2 633kb
11 184 53.6 1.2mb
12 380 102 2.5mb
13 815 253 4.9mb

ビルドでも同じで、7から上あたりではロード時間もInstantiate時間も 倍々で増えていき、そしてビルドログに出てくるサイズも倍々になっています。

なお、ビルドはmacのIL2CPPです。スマホ実機では数倍遅いことが予測され、 GameObjectが8000個もあると3秒や4秒はかかるのでは? という話になるわけです。

当たり前感はあるが...

prefabやsceneの初期化を高速にやるために、 ビルド時に参照構造を全部展開してしまう、というのは理解できる実装です。 最終的に入っているGameObject数で負荷や容量が決まる、 というのは、言われてみれば当たり前感があります。

しかし、この例のようにプレハブの入れ子が深くなると、 指数関数的に容量が増える恐れがあり、しかもそれに気づきにくいのです。

昔のUnityは入れ子にできなかったので、 それほど多くのGameObject数にはなりませんでした。 GameObjectを増やすには手間がかかるからです。 Editor上で自動配置ツールでも作らない限りは、 手間によって数が制限されます。

しかし、プレハブを入れ子にできるとなれば、先程の実験でやったように、 大した手間をかけずに凄まじい数のGameObjectを出すことができます。 1段につきわずか2個でも、13段あれば8192個にまで膨れ上がります。 手で置いたGameObjectの数は50個にもなりません。 以前に比べて「GameObjectを置きすぎて重い」という状況が起きやすくなった、と言えます。

今回のケース

なお、よく調べてみたところ、今回製品で遭遇したケースは入れ子かどうかはあまり関係ありませんでした。 問題はParticleSystemです。

先程の「ステージ」と「レンガ」の例で言うと、 各レンガに「レンガ粉砕エフェクト」のプレハブが入っており、 これがParticleSystemで作られています。 しかし、このParticleSystemのデータが信じられないほど大きいのです。 一つ置くだけで、.prefabファイルの行数が5000行近く増えます。 あの莫大な数のパラメータが全部書き込まれているからでしょう。 それが一つのステージに50個以上置いてあり、 ステージのデータが非常に大きくなってしまったわけです。

対策

「GameObject多いと遅いよ」という当たり前の話なのですが、 気がつかないうちに増えてしまうのが問題ですし、 「GameObject数に気をつけて作ろう」と言うのも現実的な方策とは思えません。 非プログラマが気軽にモノを置けるのがUnity開発の良い所であり、 非プログラマに実装や性能のことを気にしてもらうのは非効率なのです。

というわけで、現状何の対策もできていないのですが、 一つ考えているのは、 プレハブを作っちゃった後にスクリプトを走らせて、 置いてあるプレハブ情報をjsonか何かに格納し、 初期化時にそれを元にして動的にInstantiateする、という策です。

おそらく非プログラマの人がプレハブを作る場合、 「どこに」「何を」置くか、をいじるのがメインになるでしょう。 「プレハブ内にプレハブを置いて位置と角度をいじるだけ」 であるならば、 「プレハブの種類と位置と角度」だけをjsonか何かで記録するだけで足ります。 そしてその情報を元に、実行時にInstantiateするのです。 少なくともビルド後サイズと、ロード時間は改善するでしょう。

もちろん、開発時にそのまま遊べることは重要ですから、 ゲームデザイナーの作業としては、「気にせずプレハブを置く」であるべきです。 ビルド前スクリプトでjson化する等の、ゲームデザイナーに見えない形でやれれば 良いのではないかと思います。

残念ながらInstantiate時間は減らないどころか増えるでしょうが、 「問題にならない程度」の増加で済むことを期待したいところです。 多数ある場合にはコルーチンで分散してして、 スパイクを軽減することもできるでしょう。

また、配置されているモノによっては「使う寸前までInstantiateしない」 といったことも可能なはずです。 例えば「レンガ破壊エフェクト」は破壊の寸前までInstantiateを遅延できますし、 使い終わったものを取っておいて使い回すこともできます。 そういったものについては、元のプレハブに置かれていた数全部を用意する必要は ないかもしれず、総合的には処理も削減できるかもしれません。

ただ、今回の場合問題はParticleSystemであり、 プレハブが大きいということはInstantiateも重いでしょう。 それを動的に行えばゲーム中にスパイクが出ることになります。 なので、「だいたいこれくらいの数用意しておけば足りるだろう」 という数を初期化時に作ってしまい、万が一足りなくなったら スパイクしてでも動的に追加、というのが妥当な実装に思えます。

まとめ

速度より容量に配慮してほしいなあ、という個人的な感想です。 Instantiateが多少遅くなってでも、参照を保った形でプレハブデータをビルドしてほしいなあと。

ビルド後のプレハブの容量は当然ビルドしてみるまでわからず、 ビルドログでアセットのサイズを確認するなんて面倒くさいことは、 よほどの問題が起こらない限りやらないのです。

あと、ParticleSystemのパラメータ数がおかしなことになっていて、 これはさすがにもう限界なのでは?感がすごいですね。

社内勉強会をサロンにmigrationしました

淡々と続けてきたMigration Trackも、とうとう最終日になってしまいました。こんにちは、ソーシャルゲーム事業部ゲーム技研の谷脇です。

この記事はTech KAYAC Advent Calendar 2019 Migration Trackの25日目の記事です。24日目はWEB+DB Press Vol.114 に「マネージドサービスによる既存サーバの再構築」を寄稿しましたでした。

当技術ブログおよび、表の方のAdvent Calendarではは技術ブログなのに、人の行動原理について解説してみたり、カレーのきれいな食べ方の研究が載ったりと、普段我々が仕事で「技術」と呼ぶ範囲には含まれない話題も盛り上がりました。ただ僕はこういったネタも好きで、僕が主催する社内の勉強会であれば、仕事の技術、次に時代が来ると感じる話題、生活の知恵など、エモよりテクであると喋る人が言い張れるものならなんでもござれ、というスタンスでやっています。

そんな社内勉強会を、継続的に続けていくために、サロンにmigrationした話です。サロンっていうのは、オンラインサロンとかのサロンです。

社内勉強会の意義と継続性に関する課題

この記事で語る、社内勉強会の定義をしておきます。

  • 参加者が社内の方のみで構成される
  • 発表者も社内のみ
    • 社外の方を読んで話を聞く特別回もあるが、性質が違ってくる
  • 社内の案件について話したり聞くことが出来る
    • プロダクトのコードを実際に見せたり、動いているサーバを見せながら話したりも
    • 受託案件などは社内でもなかなか話すのが難しいものもありますが...
  • 月1〜週1程度の頻度で開催される

と言ったところです。

そして、何故社内勉強会をやりたいのか、という欲求に対して2つの視点があります。それは聞く側と話す側の視点です。

  • 聞く側: 知らない知見を実体験として吸える
  • 話す側: 得られた知見が実際に正しいのか客観的な視点で見てもらえる

聞く側の話はよくされますが、話す側の欲求をどう満たすか、という視点も勉強会を成立させるのに必要な要素です。人に話を聞いてもらって嬉しい、という感情的なメリットもありますが、フィードバックをもらえる事自体にも価値を感じる人も多いです。

さて、これらの事柄から社内勉強会をやることに対しての、メリット・デメリットが導き出されます。

社内勉強会ならではのメリット: 話の一般化がある程度不要

知見を吸うだけなら社外の勉強会に行くのでもいいのでは、という声もあります。なので私なりに社内でやるメリットを言うなら、社内だから話せることがあるを挙げます。

社外の勉強会やカンファレンスで話す時には、話す内容を一般化する過程を経なければなりません。内容の一般化とは、以下に示すような事情がわかっている同僚ではなくても、話したいことが伝わるようにするために内容を付け足したり、落としたりする行為です。

得られた知見を一般化すると、他の種類の仕事や、他の会社の人も知見を活かせるようになります。

具体的にどうやって話を一般化するかというと、

  • 話す事柄の詳細な定義
    • この記事のようにしつこく定義しないと、人によって認識がブレます
  • 前提条件の開示
    • 歴史的経緯など、その技術の選択を何故したのか材料になるための前提知識
  • 話したい課題の本質以外の情報の排除
    • 具体的な案件や別カテゴリの話題などを盛り込むと話がブレます
    • 似た種類の行為として、バグレポートを上げる際のバグが再現可能な最小のコードの提示が挙げられます
  • 社内の機密情報の除外
    • ビジネス上の機密情報を公開の場で言うのは様々なリスクがあります
    • 実際に動いているコードを提示するなども、この項目によって除外されます
  • なんらかの議論の答え
    • 一般化する大きな理由として、聞く人にとってためになる話ができるかがあります
    • 「困ってるんですよ〜」で終わる発表は聴衆もどう受け取っていいかわからなくなります。私個人としてはあってもいいとは思いますが、大体の人は発表している人なりの答えが聞きたいのではないのでしょうか

などが挙げられます。

ただ、ここで挙げた項目からわかるように、大変に骨が折れる作業ですし、頭を使います。また、一般化しすぎてあまりにも抽象化されてしまうと、課題と解決方法が捉えづらくなってしまう弊害もあります。今、書いていることがまさにそんな感じだなと思っております。

しかし、もし社内で事例発表をするケースだったらどうでしょうか?

具体的な案件のこともある程度話せますし、コードも例示することが出来ます。また、歴史的背景や社内の技術選択の指針のようなものも、聞いている人にはあらかじめ伝わっている事が多いため、前提の話は飛ばして話したい部分に重点を置いて話すことも出来ます。

また、社内勉強会であれば「困ってるんですよ〜」で終わってもいいと私は思っています。その場にいる人は具体的な手法に踏み込んで、解決方法を提案することが出来ます。そこから実際に手を付けてもいいでしょう。

社内勉強会でのデメリット: ずっと続けられるかどうか

社外の勉強会でも、勉強会を何度も継続的に開催できるかは悩んでいる事柄です。ただ、社内でクローズドな会だと人の入れ替わりがより起こりにくく、関わる人も入れ替わらないことから、運営をやる人が偏ります。

偏ると何が起こるかと言うと、運営をやっている人が忙しくなったり、飽きると終わります。

会社でやる勉強会は、会社が後押しする、ある意味強制的なイベントとしてやるというお墨付きがあると、継続性が緩和されることもあるのですが、カヤックは多様性と自主性を重んじていて、フラットな組織であることが是とする文化なので、強制参加の会をやるのはあまり馴染みがありません。

私も、2018年末頃に社内勉強会があまり開催されなくなっているのを見て、よーしやったるぞと引き取ってみたのですが、話題によって来る人が入れ替わったり、人数の差が激しかったり、話してくれる人を捕まえるのが難しかったりと、課題を感じて「ほんまにこれ続けられるんかな?」となって行き詰まっていました。

というわけで社内勉強会はやめてサロンをやることにしました

社内サロン

この話を相談した時に、「サロンをやればいいのでは」と弊社の人事部長に言われたときは「サロンって貴族がやるやつ?」って思いました。ですが、よく考えてみると、今抱えている課題を解決するのにぴったりな勉強会の形態だと考えるようになりました。

私が勉強会をやる際に大事にしたかったのは、以下の2つです。

  • 定期的に開催される技術の話であれば話せる場・時間があること
  • 聞きに来る人は社内の人であれば制限しないこと

これさえ守られれば、従来の「30分から1時間程度で、社内の人にスライドめくりながら話してもらう形態」にこだわらなくても良いなと感じたのでした。

そこで、世間のサロンの形態を調べると、

  • クローズドな場で
  • 1人ないし少数のコアメンバーの人が何かを話す
  • コアメンバーが出した話題に関して議論する

と解釈したので、社内勉強会向きにアレンジしてみます。

  • 社内というクローズドな場だが、社内には開けているようにする
    • オンライン中継と録画と資料公開を常にする
  • 何人かの社内で技術的に強いと言われている人を集めて、持ち回りで話す
    • 話す人集めがしんどいのを解消するための施策
    • 強い人は技術の話題に対して話しなれているはずなのと、話を聞く人を集めやすい
  • 毎週1回30分
    • 定番の番組にすると、聞きに来ることが習慣化されるのではという仮説

これでスタートしてみました。

半年やってみた感想

これは僕がはじめから意図してなかったのですが、「コアメンバーが聞きに来た人に対してお悩み相談する会」の形態をしたところ結構ウケたのでそれが続きました。

サロンで挙がった話題としては、

  • 3Dのゲームで必要な技術を挙げて、どこを会社で伸ばすかという話 by 平山さん
  • 社内のワークフロー整備してみたけれど、皆さんはどうですかという問いかけ by
  • フロントエンドエンジニアが使う技術をコロコロ変えるのはなんでか by のびー
  • IPアドレス認証やめたいけどどうすればやめられるのか by 組長
  • ↑の話に関連して実は認証プロキシを作ってるよ by れもんさん
  • チームを強くするためにいろいろ実験してるよ by 原さん

などなど、この場でしか聞けないたくさんの話が聞けました。

ただ、課題感もあって、一番は「技術的に強い人は引っ張りだこで忙しい」問題があります。なので、いつも聞きに来ている人にそろそろなにか話してみない? と打診してみたり、話した人が次に話す人を決める方式にしてみるなど、いろいろ実験しています。

継続はある程度できたとして、次の目標は「安定的に聞きに来る人を増やす」ことだと考えています。話す人の様子を見ていると、聞きに来る人がいるかどうかが不安だったり、話が聞きに来た人に刺さるかどうかが不安というのを感じます。なので、理想の状態を「どんな話題でも興味津々に話を聞く人がたくさんいる」と定義して、サロンを改善していきたいと考えています。

ちょっとエモな感じになりましたが、Tech KAYAC Advent Calendar 2019 Migration Trackは以上です。良いお年を〜。