mewlist

uGUI 描画優先度のチートシート


皆様,こんにちは!
株式会社Aiming の 土井と申します!
リードソフトウェアエンジニアをやっております!

ここ数年は,業務で Unity の uGUI を使って UI 開発をする機会が多いのですが、
Order in Layer, Sorting Layer, ヒエラルキ上の並び順……(つд⊂)ゴシゴシ
Unity では重なり順を指定する項目が多いですよね。
これらの項目間の優先度が実際にどうなっているのか理解していなかったため、調査を兼ねてチートシートを作ってみました!

描画優先度を指定するものを列挙してみた

どうやらこれらの関係を全て調べ上げれば良さそうです。

チートシート

調べました。
画像は実際に Unity の GameView で表示されたもののキャプチャです。

これで、もう UI の重なり順で悩まない!

解説

カメラ毎の優先度

  • Canvas に設定された Camera による優先度
    • ScreenSpace: Overlay 設定のものが最優先
    • 次いで Depth 値が高いものが優先

同じカメラに設定されたキャンバス同士

  • SortingLayer が高い Canvas が優先
    • 同じ SortingLayer 同士では、 Order In Layer の値が高い Canvas が優先
      • さらに同じ Order In Layer 同士では、Z 値が低い(カメラに近い) Canvas が優先

Mesh や パーティクルなど UI 以外の Renderer について

  • SortingLayer や Order In Layer の値による重なり順の制御ができる
  • ただし、SortingLayer と Order In Layer が同じ値を持つ Canvas がある場合、ヒエラルキ上の並び順による重なり順のソートは行われないので、 固有の Order In Layer 値を設定しよう

Render Queue について

  • UI の描画は、 RenderQueue = “Transparent” にて行われるため、独自のマテリアルを使用する場合は RenderQueue の値に 2501 以上を指定する必要がある

最後に

  • UI のレイヤーの間に 3D モデルを表示したりエフェクトを出したりといった要件のときには、まず全体の UI のレイヤー構成を整理して仕様化しましょう。行き当たりばったりで重なり順をいじっていると混迷を極めます
  • MeshRenderer などは標準でインスペクタ上から SortingLayer や Order In Layer が設定できません。
    • https://docs.unity3d.com/ja/current/ScriptReference/Renderer.html
    • 自前でスクリプトから設定する必要があります
      • (Order In Layer は sortingOrder というプロパティです)

おしまい。


oguma

ハートビーツさんと懇親会でLTをしました ’17 06/16


どうも、こんにちわ。

SNS幽霊部員ビリティ高めなOgumaです。
AimingではインフラをAll layerで担当しています。

先日6/16に、そのインフラに関係するMSP (Management Service Provider) である
ハートビーツ さんに 数名で 遊び 懇親会に行かせていただきました。

その際に後輩の趙くんと共にLTをやらせていただきました!!!

国内 (海外は担当が異なる) の公式サイトや、それらと連動するゲーム内から呼び出されるwebView向けのサーバなどをCephFS上に載せてみた話をしました。

さすがハートビーツさんっっ!!
いつもブログ等で勉強させていただいてる高いスキルをもった方々がいらっしゃいますね。
いろんな貴重なお話をさせていただいて、大変嬉しかったです。

また、発表した資料にさすがプロと言える鋭い質問をいくつも頂けました。

例えば

  • なぜCephなのか、Glusterではなかったのか
    • Glusterの方が高速に動いたからGlusterの方を選択した前例があったとのこと

など。 (お酒が回ってて、これ以上は思い出せませんw)

資料には書かれていないことなので、ここで補足という形で
我々がCephFSを採用した理由をまとめますと以下になります。

  • 今回の場合、I/O速度を求めなくて良いようなところに使用した
    • そのように設計した & なっている (I/0が必要なところはDB関係のみ)
  • CephがOpenStackで実績が積まれていて大部分が枯れている (LTSの安定に期待が持てた)
  • CephFSを使ってみたかった
    • 使ってみたら安定していた
  •  (今回の資料には書けていないところもありますが)高速化方法が色々把握できた
  • Kubernetes のPersistent diskでも対応している
  • スケーラビリティの高い分散ストレージClusterが必要
    • webViewや、イベントのアクセスが大量に来ることがあるため
  •  我々のGluster Storage (Redhatに買収されてからRHGSになった)の利用経験、実績がない

といった点です。

その他弊社からは
アプリケーションエンジニアより、昨今注目されているPrometheus についてLTがありました。

好評の触ってみたシリーズで、最後の刺さる点を共有してくれたところも勉強になりました。

hbstudyというITインフラ業界では有名な勉強会を開催している憧れの会社/エンジニアさんがいる会社で、スキル高いなーと再認識しました。
(人気が凄くてなかなか参加できていませんが、かなり前に個人的にもhbstudyに参加したことあります)

とても貴重で楽しい体験をさせていただけたハートビーツさんに
この場を借りて、心から感謝と敬意を表させていただきます。


菅野 明洋

社内でエンジニア読書会をやってみた!


はじめに

初めての人は初めまして!
前回の記事(第2回 Game Gatling LTに登壇してきました!)を見てくれてる人は、お久しぶりです!

大阪スタジオ インフラチームの菅野明洋です。
業務では、主に大阪スタジオのサービスインフラを担当させていただいております。

今回は、読書会を開いてみましたので、その話をまとめました。

読書会について

読書会は、集団で読書や読書に関するコミュニケーションを図るイベントです。
弊社では興味がある話題や本ごとに幾つかの読書会が開かれております。

開催してみた読書会について

今回は、SQLアンチパターンと言う本を題材にしております。
この本の読書会をするキッカケとしては、弊社の若手のエンジニアが最近読み始めたと言うことで話題に上がったことと、他のエンジニアと意見交換出来たら面白いと言う事で開いてみました。

読書会のルールについて

基本的に参加自由

  • 参加者は8〜12人程

開催時間

  •  定時後の45〜60分
    • 子育てで忙しい人も参加しやすくするために短めに設定
    • 経験上、1ページ2分換算で調整
  • 時間内(15〜20分程度)に章の全てを読みきれない場合は2日に分ける
    • 2日に分ける場合の例
      • 1日目にアンチパターンの問題点を黙読し他班に説明
      • 2日目にアンチパターンの解決策を黙読し他班に説明

用意したもの(書籍以外)

  • 付箋、筆記用具
    • 質問や意見を記入
  • ホワイトボード
    • 書籍の内容の説明や上記の付箋を並べるのに使用
  • キッチンタイマー(スマホアプリ)
    • 時間調整に使用

やり方

読書会の流れ

事前にやること

  • 読書会の日時、場所を決める

ステップ1(班分け)

  • 参加者を2班に分ける
    • 現在の決め方は座った席を順で分ける

ステップ2(読書フェーズ:アンチパターンの確認)

  • 各自担当箇所の章を黙読
    • 最近は読む際に付箋を配り、気になった点や質問したい内容を記入
    • ページ数に応じて変更しますが、大体15〜20分を目安にしています

ステップ3(説明フェーズ)

  • 読んだ箇所を別の班に説明
    • 説明する際は、図説する際はホワイトボードも使用
    • 章ごとに10分程度(計20分)の説明する

ステップ4(質問・討論フェーズ)

  • 全体を通して質問、討論を行う
    • 気になる点、分からない点を確認し解消する
    • 討論時にはアンチパターンに対してどのようにアプローチすべきなのか、他に方法が無いのかなど参加者の想いや考えなどのやり取り
    • 過去の話とかで盛り上がることもあります

読書会の様子

弊社の藤野がSQLアンチパターンの5章EAVについて説明している様子

読書会中に書き留めた付箋を集めてホワイトボードに貼っている様子(5,6章)

読書会中に書き留めた付箋を集めてホワイトボードに貼っている様子(3,4章)

ものすごく盛り上がっている時はホワイトボードにびっしり文字や図説が書かれていることもあったのですが、写真を取っていなかったため、現在記事を書きながら後悔しております。

読書会での試みと効果(参加者のフィードバック)

課題1:短時間で効果的に理解を深めたい

  • 対応策
    • 黙読後、読んでいない人に説明する
  • 効果
    • 説明する際に一度情報を整理するため、理解を深めやすくなった

課題2:質疑応答の敷居を下げる。意見を拾い上げやすくする

  • 対応策
    • 付箋を配布し、記入してもらう
  • 効果
    • 気軽に質問しやすくなった
    • 付箋を並べることで、他の参加者の質問(悩み)が見えやすくなる
    • 類似する意見があることで、他の参加者も同じ疑問を思っている等の安心感を覚える
    • 最初から質問・意見の数がわかるため進行の調整が楽になった

最後に

読書会を通して他のエンジニアと意見が交換できたので、とても参考になりました。
技術的な話のみだけではなく読書会の運用方法なども含め様々な意見を頂き、とても勉強になりました。今回の読書会では、まだ未完成な部分もあり進行が安定していないため、ブラッシュアップをしていかなくてはいけないと考えております。
毎回試行錯誤の繰り返している状態ではありますが、今後もこのようなイベントを続けて、より完成度を高めていきたいです。


vivid_muimui

サーバーサイド勉強会(2017年4月)


東京スタジオのエンジニアの小山です。

Aimingでは勉強会や読書会などがいろいろな形で行われているのですが、そのうちの1つにサーバーサイド勉強会という取り組みがあります。
この記事では、サーバーサイド勉強会自体の紹介と、4月中に行われたサーバーサイド勉強会の内容を紹介したいと思います。

サーバーサイド勉強会とは

サーバーサイド勉強会は、東京スタジオの主にrailsを書いているメンバーを中心とした勉強会で、週に1回1時間を業務時間中にとって行われます。(大阪スタジオでもサーバーサイドのメンバーを中心とした勉強会は行われています。)
内容や形式に関しては、「月初はLTを行う」というのが決まっていますが、それ以外は毎回違います。リリースノートを読んだり、ハンズオンをしたり、その時々で興味あるものホットなものをネタにやっています。

少し変わった取り組みとして、毎回常に Google Hangouts を使って社内のみですが配信をしています。忙しくて勉強会の会議室には行けないけど聞きたいという人や、大阪スタジオの人が見れるようにするためです。

参加人数は、会議室・Google Hangoutsそれぞれに10人弱ずつぐらいという規模で行われています。

4/6 LT

月初のLTです。LTの時間は5分でやっています。

  • Google Cloud Container Builderの話
  • ChainerRLを触ってみた話
  • UNIXコマンドの基礎
  • ズルいUI
  • 色の話
  • 美しい Slack 通知を飛ばす美しいコマンド
  • OSS Gateに参加した話

この記事を書くことになったのがLTの発表後だったため、公開できるように作られてないLTが多かったのですが、2つ公開できたので紹介します。

美しい Slack 通知を飛ばす美しいコマンド

https://rastamhadi.github.io/slack_notification_command_lt/

reveal-jsで表示されています. ?を押すとキーボードショートカット, sでスピーカーノートが表示されます

色の話


この回のLTは全体的にクオリティが高いものでどれも為になったのですが、個人的にUI/色の話がとても勉強になった回でした。
絵やデザインといった分野はとても苦手で避けてきたのですが、LTを聞いて頑張れば少しはできるようになるのでは?やってみよう、という気持ちができてきました。LTのあとの雑談で紹介された「ノンデザイナーズ・デザインブック」は速攻で注文し少しずつ読み進めてます!

4/13 Google Cloud Functions で Slack のスラッシュコマンドを作ろうハンズオン

https://cloud.google.com/functions/ 「Google のインフラストラクチャに基づくサーバーレス アプリケーション」

AWSでいうところのAWS Lambdaなのですが、そのCloud Functionsを使ってサーバーレスでお手軽にslackのスラッシュコマンドを作ってみよう、というハンズオンです。
ほかのサービスを連携したりせずに、ハードコードされたリストからランダムに選択しslackに返す、という実装をしました。

実際作られたものの一部が↓です。(どちらも僕が作ったものではありません>< )

シンプルな実装ではありますが、slackとgcpの画面をポチポチ設定して、少しのコードを書くだけでスラッシュコマンドが作成できて、本当にお手軽でした。
ハンズオンではやらなかったのですが、 Firebase Realtime Databaseを使ったスラッシュコマンドの作り方も紹介されました。

とてもお手軽だし便利で最高でした!
ですが、どんなスラッシュコマンドを作るかのアイディアが出ずに僕はまだ何も作れてないです!

4/20 Mastodonのコードリーディングとかアーキテクチャを覗いて見よう

流行りのMastodonのコードリーディングをする回でした。
https://github.com/tootsuite/mastodon
最初にMastodonがどういうものかという話がされ、rake stats -> Gemfile -> schema.rb -> modelを少し、という順に眺めていきました。
大半はGemfileを眺める時間に費やされましたが、知らないGemが多くrubyのエコシステムの充実さスゴイなと改めて思いました。

実装の詳細は全然見れなかったですが、「このgem知らなかった!便利!」とか「この書き方いいね」「この命名なるほど!」など十分有意義なコードリーディング回でした。

4/27 RailsGuideのActiveSupportのページ読み

RailsGuidesの「Active Support コア拡張機能」のページを上から眺めていくという回でした。
本家翻訳版

blank?tryなどのおなじみのが多いですが、上から順番に眺めていくと知らなかったものも結構ありました。
知らなかった中でぼくが便利そう!と思ったのは

この2つでした。

Object#instance_valuesはその名の通りinstanceのインスタンス変数をハッシュにして返してくれます。

class C
  def initialize(x, y)
    @x, @y = x, y
  end
end

C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}

使う機会はそんなに多くはないかもしれないですが、ActiveRecordの#attributesと似た振る舞いですし色々使いみちはありそうです。

String#squishString#stripと似てるのですが、冒頭と末尾のホワイトスペースを除去するString#stripの挙動に加えて、文字列中の連続したホワイトスペースを一つに減らしてくれます。

" \n  foo\n\r \t bar \n".squish # => "foo bar"

RailsGuidesは全体通すとボリュームが多いですが、見るたびに既に見たことが有るページでも新しい発見があるので、定期的に読み直さなきゃなと改めて思いました。

おわりに

月イチでLTを行う現在の勉強会のスタイルなって半年ほどなのですが、正直なところLTのネタ出しは毎回苦戦しています。
ですが、
LTをする -> 勉強会・LTを通して新しいことを知る -> ネタ出しのために勉強する -> LTをする(最初に戻る)
という良いサイクルが自分の中に出来上がりつつあって、現在の勉強会のスタイルは個人的にとてもありがたいスタイルだなと感じています。

まだ勉強会で吸収することばかりですが、そのうち新しいものを伝えていく側になれたらと思っています!


Webブラウザ版 『剣と魔法のログレス』のサーバーマシンリプレイスのお話し


はじめまして、大阪スタジオのサーバーエンジニアの太田と申します。

Webブラウザ版 『剣と魔法のログレス』(以下ログレス)は2011年にリリースして、おかげさまで今年で運営6年目になります。

そんなログレスも、そろそろマシンの老朽化やスペック不足が気になり始めたので、2017年3月にサーバーマシンのリプレイスを実施しました。

今回はそのお話しになります。

リプレイスの目的は?

  • 経年劣化によるハードウェアトラブルの回避
    • 全サーバーマシンを新しいハードに乗せ替える
  • 物理サーバーから仮想サーバーへ乗せ替え
    • サーバーの追加や削除が簡単に行える。
    • トラブル時の復旧も楽になる。
  • サーバーOSのバージョンアップ
    • OSのサポート期限が切れるため
  • 最新のクラウドサービスへ移行する
    • セキュリティの強化、保守性の向上
    • 構築や設定コストの削減

リプレイスに向けてのスケジュールと準備内容は?

  • 5か月前から準備開始
    • リプレイス内容の決定
    • タスクの洗い出し
    • スケジュールの決定
  • 4~3か月前
    • 物理サーバーと仮想サーバーでの各種ベンチマーク比較
    • 必要スペックの洗い出し、費用の見積もり
    • 動作検証用環境での各種動作チェック
  • 2か月前
    • 新クラウドサービス契約
    • OSのセットアップ、ネットワーク設定
  • 1か月前
    • ゲーム環境の構築
    • 各種動作チェック(課金認証等)
    • 本番当日のタイムスケジュールの作成
  • 1週間前
    • ステージング環境のリプレイス
  • 当日
    • 本番環境のリプレイス
    • DBとログの引っ越し
    • 本番環境での各種動作チェック

苦労したこと

  • バージョン違い問題
    旧環境で動作していた各種サーバープログラムが、OSバージョンやソフトウェアバージョンの違う新環境で同じように動作しませんでした。
    使用してるライブラリが新しいOSでサポートされておらず、対応に苦労しました。
  • 仮想化で発生した不具合
    仮想サーバーに乗せ替えてから、ゲームサーバー内の全てのスレッドがスリープ状態になる不具合が発生しました。
    調査するとスレッドをスリープする処理とウェイクアップする処理に問題があり、タイミングによっては今回の症状が発生する可能性があることが判明しました。
    物理サーバーではCPUのコンテキストスイッチの動作が安定していて問題が起こりませんでしたが、仮想サーバーにすることで不安定になって、問題が表面化しました。

良かったこと

  • 新たに契約した新クラウドサービスは、ネットワーク転送速度が10Gbpsなので、ファイルの転送速度が速くなり、メンテナンス作業が高速化しました。
  • マシンのスペックアップに伴い、必要マシン台数が減り、結果的に運用コストが軽減しました。
  • 利用できるメモリが増え、今後のゲームコンテンツの拡張がしやすくなりました。

まとめ

私にとってはサーバーのリプレイスは初めての経験でしたが、時間をかけて事前の動作検証や当日の作業準備などを行った結果、ログイン障害や課金障害等の大きな問題もなくリプレイスを終える事が出来ました。

今回の経験を通して、改めて事前の入念な準備と動作検証が重要だと思いました。

以上、ログレスのサーバーマシンリプレイスのお話でした。


onose

2017春のインターンシップ!


こんにちは! 採用担当の小野瀬です。

本日は東京本社で行われた『2017春のインターンシップ』について、
メンターを担当した2名(エンジニア/プランナー)の話も交えつつご報告させて頂きます!

インターンシップについて

Aimingではゲーム業界を志望している学生を対象に、ゲーム制作を体験してもらうインターンを行っております。
なかなか学生では経験できないアジャイル開発やプロのアドバイスなどを体験し、ゲーム業界により一層の興味を持ってもらうことがこのインターンの目的です。

今回は約2週間という短い時間の中、4名の学生に『Aiming流ゲーム開発』を体験してもらいました。(プランナー1名+エンジニア3名)

アジャイルについて

エンジニアの小山です。
今回のインターンでは、Aimingでの実際の業務フローに近い形を経験してもらうためアジャイル開発を用いて開発してもらいました。

  • 朝会・夕会
  • カンバン
  • トレードオフスライダー
  • ポイントによる見積もり
  • バーンダウンチャート
  • スプリント計画ミーティング
  • ふりかえり

上記のような内容に取り組んでもらいました。
中でもトレードオフスライダーやバーンダウンチャートは2週間という短い期間でもとても効果的に作用していました。

日々更新するバーンダウンチャートによって間に合わないかもしれないことが可視化され、では何を入れて何を諦めるのかという判断の手助けにトレードオフスライダーが役立っていました。

中にはメリットを感じられにくかったり、そもそも2週間の開発では必要ないだろうと思うものもあるのですが、Aimingの普段の業務フローや開発の雰囲気というのは感じてもらえたのではないかと思います。

プランナーについて

プランナーの占部です。
Aimingはゲーオタ採用を行っていて、皆で同じゲームをプレイし研究する文化があります。
今回のインターンではそんなAimingらしさを持ち帰ってもらうことを目的とし、以下のようなゲーム分析の時間を設けました。

  • 2日間
  • 合計3時間~4時間ほど
  • ぐるぐるイーグルの序盤を皆でプレイ
  • ホール(マップ)にある要素から製作者の意図やユーザーへの狙いを読み解く

プランナーだけでなくエンジニアも参加し、いろんな視点からゲームの仕組みを考える有意義な時間となったのではと思います。
また、プランナーは他にこのような内容も行いました。

  • メンターが仕様書のレビューを行う
  • 隙間時間に最近はじめたゲームの感想を書く
  • 社内の現ディレクターに今回作成したゲームを講評してもらう

特に現ディレクターの講評は非常にためになったと学生から大好評でした。
たくさんゲームを遊ぶだけでなく、感想を書いたり分析したりと「アウトプットする」ことを意識して、今回の経験を今後のゲーム制作に役立てて欲しいと思います。

エンジニアについて

ふたたび小山です。
エンジニアメンバーには下記にもチャレンジしてもらいました。

  • 学生同士によるGithubのPullRequestでのコードレビュー
  • UniRxの導入

共通した目的はエンジニアとして現状に満足せずもっと成長して欲しい、というものです。
コードレビューを通して他人のコードを見る・他人にコードを見られるという習慣をつけてもらい、UniRxの導入を通してOSSの存在を意識してもらう、という狙いのもとやってもらいました。

エンジニアの三名は、学校でのチーム制作を通してゲームを作り上げる力はとても素晴らしかったのですが、そこで満足してしまうとエンジニアとしての基礎的なスキル面があまり伸びなくなってしまうと思ったので、上記にチャレンジしてもらいました。

最終日のふりかえりや面談の際に

  • もっと読みやすいコード書く・設計きちんとする
  • UniRxのコード読む・別言語で実装し直してみる

といった内容のことを学生から聞けたので、目的は達成できたのかなと思っています。

完成品

上記のような取り組みの結果、今回のインターンでは画像のような『4人対戦のアクションゲーム』が完成いたしました。
初めてのことだらけの開発で、参加学生の方々は大変なことも多かったインターンだったかと思います…!
しかしその分『多くの知識を身に着ける事ができた!』『早く次のチーム制作で試したい!』と満足して頂くことができました。

Aimingでは今後も定期的にインターンを実施してまいります。
もしご興味がある方は、下記よりご応募ください!
https://recruit.aiming-inc.com/


UnityのGCはどんな実装になっているのか


こんにちは。Aiming エンジニアの久保田です。

僕の携わっているプロジェクトでは、近頃、Unity製クライアントのパフォーマンスの調査や改善を行っている最中です。
プロファイラを眺めていると、僕達が書くアプリケーションレイヤのコードが目立って遅い、ということは珍しいのですが、代わりにC#世界のスパイクとしてよく顔を出すのが、GC実行時間です。

C#は、タイプセーフでありながら人間にやさしく、getter/setter、async/await、Rx、ロケットなラムダ式、他他他…最新型の言語への影響も多大な、ファッション的にも◎な言語です。しかし、闇雲に全ての機能をタダで……というわけにはいかず、ことパフォーマンス面においては、GCというなかなか高い代償を支払うことになりかねないわけですね。

結論としては、UnityのGCは、皆が期待していたほど高性能ではなく、現状では僕達が書くC#が発生させるGCのインパクトは無視できない大きさになります。

そこで、GCのインパクトの少ないコードベースにしていく必要があるわけですが、コードを書く際、内部の実装をある程度理解していると適切な判断がしやすくなります。

今日は、そんなUnityのGCがどんな実装になっているのか、主に3つの方法で調べた結果を解説してみます。

  • 走らせてみる
  • ILコードや、IL2CPP AOTコンパイル後のC++コードを読んでみる
  • IL2CPP のvmをステップ実行してみる

注:ただし、Unityのロードマップには既にGCの差し替えが予定されています。近い将来、ここで紹介しているGCの性能はさらに改善されることと思われます。

Stop the World

さて、リアルタイム性の要求されるアプリケーションでは、GCが性能上の問題になるとかならないとか、よく噂されています。

なぜGCが目の敵にされるのか。これは、GCのスループットが良いとか悪いとかの問題ではなく、たとえどんなに真面目にひたむきにGCが仕事をしていたとしても、彼の仕事が他人のメモリを管理することである以上、管理下のスレッドを全て停止させるタイミングが必ずやってくる、という事情があるためです。

つまり、GCによるメモリ管理を利用しているアプリケーションは、あるとき全ての動作が停止する、これは俗に「Stop the World 」というかっこいい名前で呼ばれ、恐れられています。

Stop the World が発生している間、Unityのゲームはメインスレッドやその他(マネージドな)バックグラウンドのスレッドも実行できず、時が止まってしまいます。これが長引いたり、初動が遅れた処理が間に合わなければ、ユーザが目にするのはカクカクの描画や操作のひっかかり、というわけです。

一方、GCは歴史が古く、競争も激しい分野なため、このStop the World のインパクトを抑える工夫がたくさん編み出されています。
言語組み込みのGCでは、オブジェクトを寿命ごとに分けて管理する世代別GCや、Stop the Worldを段階的に実行するIncremental GC などが搭載されていることが多いようです。Javaに至っては、かなりバージョンアップを繰り返しており、全体のスループットを落とす代わりにGCを並列に動作させるフェイズを増やすコンカレントGCなども搭載されていました。

さて、僕達のUnityのC#ランタイムはどんなGC実装が使われているでしょうか? 気になります。

フルGCが毎回走る

Unity Blog の2015年の記事、Garbage collector integration によれば、Unityでは、Mono/IL2CPP どちらのランタイムも Boehm-Demers-Weiser というGC が使われている、とあります。

Boehm GC は、オープンソースのGC実装で、特定の言語への組み込みを想定したものではない代わりに、移植性が非常に高いつくりだと紹介されています。
ただし、機能面、性能面では言語組み込みGCには及ばないという評判もみかけ、実際、 オープンソースの.NET実装であるMono のGCは、Boehm GCから独自実装のSGenへと乗り換えを行いました。

僕の現行のプロジェクトで使用しているUnity(5.5.3)では、どうなっているのか、ちょっと確認してみましょう。

適当なUnityプロジェクトを作成し、C# の GC.Collect(); をスクリプトの任意の場所に仕込み、それをiOSビルドします。
Xcodeプロジェクトが生成されるので、プロジェクトを開き、Xcodeのシンボル検索で 「GC_Collect_m」を探してみると、

// System.Void System.GC::Collect()
extern "C" void GC_Collect_m2249328497 (Il2CppObject * __this /* static, unused */, const MethodInfo* method)
{
    {
    int32_t L_0 = GC_get_MaxGeneration_m1986243316(NULL /*static, unused*/, /*hidden argument*/NULL);
    GC_InternalCollect_m479047119(NULL /*static, unused*/, L_0, /*hidden argument*/NULL);
    return;
    }
}

ありました。
今見ているものは、C#標準ライブラリの GC.Collect が実機で実行されるときの姿です。元は System.dll に含まれるC#のバイトコードだったものが、C++コードへ姿を変えています。

iOSビルドはじめとしたUnityのIL2CPP ランタイムでは、C#コンパイラが吐いたバイトコードをそのままVMで実行するわけではなく、このようにあらかじめILコードを力技でC++コードに変換しています。つまり、実機上で走っているのはネイティブC++コードなんですね。

この機能のおかげで、ブラックボックスなはずのVMの動きをそこそこ深くまで読むことができ、Xcodeのデバッガもフルに使えるため、中身の動きを知るためにとても役に立ちます。

まずは ここの GC_Collect_m2249なんちゃらかんちゃらにブレイクポイントを張り、ステップ実行してみましょう。

何度かステップインしていくと、以下の場所へ来ます。

SampleIL2CPP`il2cpp::icalls::mscorlib::System::GC::InternalCollect:
0x101370cb0 <+0>: pushq %rbp
0x101370cb1 <+1>: movq %rsp, %rbp
-> 0x101370cb4 <+4>: popq %rbp
0x101370cb5 <+5>: jmp 0x101369d90 ; il2cpp::gc::GarbageCollector::Collect at BoehmGC.cpp:60

止まっているコードがアセンブリなのは、いよいよIL2CPPのVM部分のコードに到達したためです。これより下のソースはXcodeプロジェクトに含まれていません。

しかし、よく見てみると、親切にもソースコードのファイル名やシンボルが載っていました。

il2cpp::gc::GarbageCollector::Collect at BoehmGC.cpp:60

実は、この辺の libil2cpp のvm部分のソースファイルは、Unityに含まれています。

/Applications/Unity/Unity.app/Contents/il2cpp/libil2cpp/gc/BoehmGC.cpp (Macの場合) を開いてみると……

void
il2cpp::gc::GarbageCollector::Collect(int maxGeneration)
{
    GC_gcollect();
}

僕の書いた GC.Collect は、 @GC_gcollect@ という関数を引数なしで実行するという実装になっていました。
(上記では、GC.Collect から辿っていますが、メモリの圧迫がトリガーになった場合も同じ関数が実行されていた)

引数に渡ってきた世代番号を無視しているのがちょっと気になりますが、今は目を瞑りましょう。
この Gc_gcollect()関数が置かれているのは以下のファイルです。

/Applications/Unity/Unity.app/Contents/il2cpp/external/boehmgc/alloc.c

boehmgc というディレクトリ名ですね。中身をみると、現行のUnityはやはり Boehm GCを使っていることがわかります。

この、Boehm GC 内のGC_gcollect の先をステップインして掘り進んでいくと、以下の関数に辿り着きます。

/*
* Assumes lock is held. We stop the world and mark from all roots.
* If stop_func() ever returns TRUE, we may fail and return FALSE.
* Increment GC_gc_no if we succeed.
*/
STATIC GC_bool GC_stopped_mark(GC_stop_func stop_func)
{
// 省略
STOP_WORLD();
// 省略
START_WORLD();
// 省略
}

これは、GCが管理しているメモリに対してルートから辿れる参照にマークをつけるフェイズですが、STOP_WORLD というかっこいいマクロによって、スレッドを止める処理が差し込まれていることが確認できます。

さらに、この辺りの処理は、 `GC_Increment` が真のとき、ちょっとずつ実行するという挙動になるようなのですが、Xcodeでこの変数の値を追ってみると、

0が入っています。ということは、Incremental GCは有効になっていません。この値、確認した限りでは、プリプロセッサか、GC_enable_incremental() を呼ぶことでしか変更されていないので、少なくとも今見ている環境では 毎回、フルGCが走っているとみてよさそうです。

  • Unity上でのGC処理は、Boehm GCが使われている
  • 世代別やIncremental の機能は特に使われておらず、毎回フルGCが走っている可能性が高い
  • GCの実行中は Stop the World する

注: 上記の結果は、Unity 5.5.3 でのもの。

アプリケーションのC#が引き起こすGCインパクトは思いのほかでかい

どうやら、Unity の GCによるメモリ回収は、一括して行われるようです。この辺りが、GCが一度実行されると一気にスパイクとして現れる原因と推測できます。

さて、GCがボトルネックになっている場合、GCの首をすげかえる、GCをやめる、といった選択肢をとりあえず除外するならば、アプリケーションレイヤでの対策にどれくらいの意味があるのでしょうか。

Unity上で 何もないシーンを作成し、純粋にアプリケーションのC#で classのnew を繰り返した結果を見てみました。

高々1000回程度のclassのnewで、頻繁に8ms弱のGCが発生しています。

この結果の注目すべきところは、下のグラフ、アプリケーションがヒープを要求しなかった場合にはGCスパイクがまったく発生しなかったという点で、これはアプリ層のC#次第で GC発生頻度が大きく変わることを示唆しています。

また、Unity内のUnityEngine.Object をはじめとしたオブジェクト達は、DestroyしてもすぐにはGC対象にはならず、プーリングされるような振舞いをしているため、実際のゲームでもアプリ層のC#の影響はなかなか大きくなり得ます。

struct vs class

次に気になるのは、GCのマーク&スイープが走っているときを除いた、オブジェクトが管理されること自体のオーバヘッドです。

コンパイル後に生成されるコードを見比べることで、 値型と参照型、sturct と class の違いを比較してみたいと思います。

こんな感じのC#コードをつくり、

//(中略)
var c = new FatClass();
var s = new FatStruct();
//(中略)

以下のコマンドで IL2CPPのAOTコンパイラを走らせます。

$ mcs -target:library ./Hoge.cs
$ mono /Applications/Unity/Unity.app/Contents/il2cpp/build/il2cpp.exe \
--convert-to-cpp \
--enable-symbol-loading \
--development-mode \
--assembly='Hoge.dll' \
--generatedcppdir='/Users/rkubota/tmp/Hoge'

すると、 上記のC#コードは、下のようなコードに展開されました。

//(中略)
FatStruct_t680642026 V_0;
memset(&V_0, 0, sizeof(V_0));
(中略)
FatClass_t2447623899 * V_1 = NULL;
{
Initobj (FatStruct_t680642026_il2cpp_TypeInfo_var, (&V_0));
FatClass_t2447623899 * L_0 = (FatClass_t2447623899 *)il2cpp_codegen_object_new(FatClass_t2447623899_il2cpp_TypeInfo_var);
FatClass__ctor_m162577467(L_0, /*hidden argument*/NULL);
V_1 = L_0;
return;
}
(中略)

これが さきほどの new です。
こうしてみると、structのnewとclassのnewは、C#世界でのシンタックスは同じでも、vm内での仕事は大きく違っていることがわかります。

structのnewは、単純なC++世界の値へ展開されており、スタックへ変数を確保するだけ。
対して class は、管理のためのコードが余分に生成されています。

なかでも、il2cpp_codegen_object_new という関数は、GCへメモリを要求し、初期化する処理になっています。この関数を辿っていくと、Boehm GCによって `pthread_mutex_lock` が行われている箇所があります。

classをnewするたびにロックが発生しているとは、これを見るまで意識していませんでした。structと比較するとnewには少なからずオーバーヘッドがあるということは言えそうです。

値渡し vs 参照渡し

続いて、参照をつけかえることによるオーバーヘッドが存在するか調べてみます。

// ごく単純な値渡しのメソッド
static long PassValue(FatStruct v)
{
return v.M00 + v.M10 + 1;
}

// ごく単純な参照渡しのメソッド
static long PassRef(FatClass v)
{
return v.M00 + v.M10 + 1;
}

これが展開されたものが以下です。

// System.Int64 Hoge::PassValue(FatStruct)
extern "C" int64_t Hoge_PassValue_m3398056464 (Il2CppObject * __this /* static, unused */, FatStruct_t680642026 ___v0, const MethodInfo* method)
{
{
int64_t L_0 = (&___v0)->get_M00_0();
int64_t L_1 = (&___v0)->get_M10_10();
return ((int64_t)((int64_t)((int64_t)((int64_t)L_0+(int64_t)L_1))+(int64_t)(((int64_t)((int64_t)1)))));
}
}
// System.Int64 Hoge::PassRef(FatClass)
extern "C" int64_t Hoge_PassRef_m1560133231 (Il2CppObject * __this /* static, unused */, FatClass_t2447623899 * ___v0, const MethodInfo* method)
{
{
FatClass_t2447623899 * L_0 = ___v0;
int64_t L_1 = L_0->get_M00_0();
FatClass_t2447623899 * L_2 = ___v0;
int64_t L_3 = L_2->get_M10_10();
return ((int64_t)((int64_t)((int64_t)((int64_t)L_1+(int64_t)L_3))+(int64_t)(((int64_t)((int64_t)1)))));
}
}

値渡しと参照渡しは、さほど違いが見られませんでした。参照渡しだから何かGC管理のための特別なコードが生成される、ということはないみたいですね。

これはおそらく、Boehm GCが保守的GCという仕組みを採用しているためで、GC内部で使用中のメモリのうち、参照っぽいものをなるべく保守的に自動判定するアルゴリズムが働いてくれるため、アプリケーションがわざわざ参照が増えたり減ったりをGCに知らせる必要がないようです。

これは、関数への参照渡しだけでなく、プロパティへの代入時も同じでした。

Swiftのような参照カウント方式でメモリを管理する環境では、参照が増えたり減ったりする時点でオーバーヘッドが発生しますが、保守的GCの場合、オーバーヘッドは後から一括で支払う、というわけですね。

わかったこと

ここまででわかったことは以下です

  • UnityのGC は Boehm GC
  • Incremental や Generational の機能はなく、毎回フルGCが走っていると思われる
  • classのnew はstructに比べて遅い( ミューテックスのロックを通る)
  • 参照渡しや、参照型のプロパティ代入は特別なオーバーヘッドはない。(Write barieerもない)

一括してやってくるフルGCによるスパイクへの対策としては、まず毎フレーム生まれるようなゴミを生成しないようにすることが重要です。

この辺の具体的な対策の話は数えるとたくさんありそうですが、長くなってきたので、またの機会に譲りたいと思います。
それではまた!!!!!

参考:


入社2週間の新卒エンジニアが紹介するAimingの新人研修


こんにちは。2017年度新卒エンジニアの後藤です。

今回は新卒入社して2週間経過した私の体験等をもとにAimingの新人研修の内容について紹介します。

現在就職活動をされている方や、入社後に新人がどのようなことを研修で学んでいるか気になる方に参考にしていただければと思います。

新人研修の流れについて

Aimingでは入社後1か月かけてオンラインゲーム開発にかかわる人材になるための研修を行っています。最初の数日間に社会人としてのビジネスマナーの研修やワークフローについて学び、その後はゲーム開発に関わる研修を行います。

新人研修の様子

オンラインゲーム開発に関するリテラシー研修

オンラインゲーム開発を行う上場企業の一員として知っておかなければいけない事柄として以下の項目を学びます。

  • オンラインゲームの歴史と仕組み
  • セキュリティポリシー
  • コンプライアンス

大学や専門学校では学生どうしが演習等でゲーム制作をする機会も多いですがセキュリティポリシーやコンプライアンスを意識することは無いのではないでしょうか。

研修を通して、プロジェクトの一員であるとともに企業の一員であることを認識させられます。

職種毎の紹介

デザイナー・プランナー・エンジニア等、現場で各役職に就いている方々を講師に招いて講師が実際にかかわったタイトルなどを例に以下の項目を学びます。

  • 各職種の業務内容
  • Aimingにおける職種毎の心構え
  • 他の職種とのやりとりで留意すべきこと

それぞれの職種に属する新人の意識を高めるのはもちろん、他の職種に属する新人にも業務内容を共有することで互いに連携を取りやすくします。

また、プランナーからエンジニアへ留意してほしいことなども共有し、各職種をまたいだコミュニケーションを円滑に行う方法なども学びます。

その他

新人研修の後半にはスクラム体験をはじめとするグループワークや、自己紹介LT大会を実施します。

自己紹介LTでは自分の経歴について語るもよし、好きなゲームやアニメなどと絡めながら自分を紹介するもよしと、社内の方々にとって新人との接点を知ってもらう機会になっています。

研修を受けての感想

まだ全研修内容の半分を終えた段階ですが、私が大学時代に体験したゲーム開発と比べると規模の大きさも制作する立場も違うので、研修を通して新しい知見を得る日々を送っています。

とはいえまだ「知っている」の段階なので、得られた知見を現場で活かせるように意識しつつ残りの研修にも臨みたいと思います。


Unity5.6.0で追加されたTest RunnerのPlayModeを使ってみた


こんにちは。大阪スタジオ エンジニアの西村です。

Unity 5.6.0からTest RunnerにPlayModeが追加されました。以前からあったEditModeのテストではカバーできなかったフレームをまたぐ非同期処理などのテストが可能になります。ざっくり試してみた結果をまとめてみました。この記事ではUnity 5.6.0f3を使用しています。

PlayModeを有効にする

初期状態ではPlayModeが無効になっているので有効にします。まずWindow – Test RunnerでTest Runnerウィンドウを開きます。

PlayModeタブを選択すると”Enable playmode tests”というボタンがあり、これを押した後でUnityを再起動する必要があります。

これでPlayModeが使えるようになりました。

PlayModeでテストを実行してみる

Create Playmode test with methodsを押すとテンプレートスクリプトが作成されます。

NewPlayModeTest.cs

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class NewPlayModeTest {

    [Test]
    public void NewPlayModeTestSimplePasses() {
        // Use the Assert class to test conditions.
    }

    // A UnityTest behaves like a coroutine in PlayMode
    // and allows you to yield null to skip a frame in EditMode
    [UnityTest]
    public IEnumerator NewPlayModeTestWithEnumeratorPasses() {
        // Use the Assert class to test conditions.
        // yield to skip a frame
        yield return null;
    }
}

スクリプトがコンパイルされるとPlayModeにテストが追加されます。

とりあえず手を加えずにRun Allしてみると。シーンがテスト用のものに入れ替わりUnityがPlay状態になります。しばらく待つとテストが実行され結果がTest Runnerウィンドウに反映されます。

当然なにもないので成功です。

次は失敗させてみましょう。適当にAssertを追加します。EditModeのテストと同じ感覚で書くことができます。

NewPlayModeTest.cs

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class NewPlayModeTest {

    [Test]
    public void NewPlayModeTestSimplePasses() {
        // Use the Assert class to test conditions.
        Assert.IsTrue(false, "失敗させてみる");
    }

    // A UnityTest behaves like a coroutine in PlayMode
    // and allows you to yield null to skip a frame in EditMode
    [UnityTest]
    public IEnumerator NewPlayModeTestWithEnumeratorPasses() {
        // Use the Assert class to test conditions.
        // yield to skip a frame
        Assert.IsTrue(false, "失敗させてみる");
        yield return null;
    }
}

はい、失敗しました。

PlayModeらしいテスト

ここまではEditModeでも実行できる内容です。次にEditModeでは出来なかったフレームの更新がある状態になっているか試してみます。

NewPlayModeTest.cs

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class NewPlayModeTest {

    [Test]
    public void NewPlayModeTestSimplePasses() {
        // Use the Assert class to test conditions.
    }

    // A UnityTest behaves like a coroutine in PlayMode
    // and allows you to yield null to skip a frame in EditMode
    [UnityTest]
    public IEnumerator NewPlayModeTestWithEnumeratorPasses() {
        // Use the Assert class to test conditions.
        // yield to skip a frame
        yield return null;
    }

    [UnityTest]
    public IEnumerator 複数フレームに渡ってテストできる() {
        // Time.timeは同一フレーム中は同じ値を返す
        var startTime = Time.time;
        System.Threading.Thread.Sleep(1000);
        Assert.AreEqual(startTime, Time.time);

        yield return new WaitForSeconds(1f);
 
        // フレームが変わっていればTime.timeの値が変わっているはず
        Assert.AreNotEqual(startTime, Time.time);
        yield return null;
    }
}

複数フレームに渡ってテストできることがわかります。

MonoBehaviourTest

MonoBehaviourTestと言うものがありますが詳しい使い方がわかりませんでした。動作テストに使用したコードは以下の通り。

ExampleBehaviour.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ExampleBehaviour : MonoBehaviour {
    protected int counter = 0;

    // Use this for initialization
    void Start () {
 
    }
 
    // Update is called once per frame
    protected void Update () {
        counter++;
    }
}

ExampleBehaviourTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TestTools;

public class ExampleBehaviourTest : ExampleBehaviour, IMonoBehaviourTest {
    public bool IsTestFinished { get; private set; }

    // Update is called once per frame
    new void Update () {
        base.Update();
        Debug.Log(counter);
        if (counter > 10)
        {
            // ここで止めておかないと他のテストの裏でも動き続ける
            gameObject.SetActive(false);
            IsTestFinished = true;
        }
    }
}

NewPlayModeTest.cs

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class NewPlayModeTest {

    [Test]
    public void NewPlayModeTestSimplePasses() {
        // Use the Assert class to test conditions.
    }

    // A UnityTest behaves like a coroutine in PlayMode
    // and allows you to yield null to skip a frame in EditMode
    [UnityTest]
    public IEnumerator NewPlayModeTestWithEnumeratorPasses() {
        // Use the Assert class to test conditions.
        // yield to skip a frame
        yield return null;
    }

    [UnityTest]
    public IEnumerator 複数フレームに渡ってテストできる() {
        // Time.timeは同一フレーム中は同じ値を返す
        var startTime = Time.time;
        System.Threading.Thread.Sleep(1000);
        Assert.AreEqual(startTime, Time.time);

        yield return new WaitForSeconds(1f);
 
        // フレームが変わっていればTime.timeの値が変わっているはず
        Assert.AreNotEqual(startTime, Time.time);
        yield return null;
    }

    [UnityTest]
    public IEnumerator MonoBehaviourのテスト() {
        yield return new MonoBehaviourTest<ExampleBehaviourTest>();
    }
}

MonoBehaviourTestを利用してMonoBehaviourを実行することも可能です。IMonoBehaviourTestを実装したMonoBehaviourがIsTestFinishedがtrueを返すことでテストが完了します。IsTestFinishedがfalseのまま30秒経過するとタイムアウトしますが、タイムアウトしても完了として扱われてしまいます。

MonoBehaviourTest実行中にAssertを呼び出してしまうと以後のテストが失敗してしまうため、Assertを呼び出すことでテスト単体を失敗させる事ができません。MonoBehaviourTestについてはそもそも使い方を間違えている可能性もあるので深追いしないことにしました。

Playerでテスト

左上のRun AllではUnity Editor上のPlayモードで実行されますが実際のPlayer上でも実行することが出来ます。

Build SettingsのSwitch PlatformでAndroidに切り替えてみると、右上のボタンがRun all in player(Android)に変わります。

実行してみるとビルドが走りAndroid端末でテストが実行されます。実行結果がUnity上のTestRunnerにフィードバックされないのは残念ですが手軽に実行することが出来ます。

まとめ

今回調べた中で分からなかった事

  1. UnityEngine.TestTools.MonoBehaviourTestの扱い方
  2. コマンドラインからPlayModeのテストを実行する方法

MonoBehaviourTestの扱い方が分からなかったため不完全燃焼ではありますが、PlayModeを利用することで今まで出来なかった非同期処理のテストも書くことが出来るようになりました。また、コマンドラインからの実行が出来ないのでCIに組み込む事が出来ませんが、コードレビュー時など手元でサクッと実行できるのは便利です。

今後もPlayModeテストをCIに組み込むことを視野に入れてウォッチを続けていきます。


久保 翔太

GCE Local SSD向けのチューニング


こんにちは、インフラエンジニアの久保です。

今回は、GCE(Google Compute Engine)のLocal SSDのチューニングとベンチマーク結果についてご紹介したいと思います。

Local SSDとは

GCEではPersistent Disk(ネットワーク ディスク)、Local SSD、Cloud Storage Bucket、RAM Diskの4種類のディスクサービスを提供しています。
今回利用したLocal SSD はPersistent Disk(ネットワーク ディスク)と異なり、仮想マシンインスタンスのホストとなるサーバーに搭載されているSSDに直接接続されているため、非常に高速です。
Persistent Diskに比べて低レイテンシかつ高IOPSであるため、非常に高い性能が求められている環境においては非常に有用かと思います。
また、今回の試験では利用していないのですが、RAM Diskはメモリ上にデータを格納するため、Local SSDより高い性能が期待できます。
続きを読む