gRPC

gRPCとREST APIでスループットを比較する(Unity編)

はじめに

C#のコンソールアプリとGoのサーバーサイドにて表題のスループットを計測したこちらをベースにUnity上で動作させた場合の検証を下記に示します。

ソース

サーバーサイドのソース(Go)は先の検証と同じものを使用します。
クライアント側はUnity上で動作するように変更しました。
(実行しようとしている処理の内容は一緒ですが、Unity上で動作するように必要に応じて変更しました。)
下記をMonoBehaviourインスタンスから呼び出すようにします。

gRPC Unary RPC

        public static void DoUnaryTest(int jobCount)
        {
            var channel = new Channel(Host, Port, ChannelCredentials.Insecure);
            var client = new DataManager.DataManagerClient(channel);

            for (int i = 0; i < jobCount; i++)
            {
                var res = client.UnaryTest(new RequestMessage { Content = "TestContent" + i });
                // レスポンス受取後、特に何もしない
            }

            channel.ShutdownAsync().Wait();
        }

channel.ShutdownAsync().Wait()を除き、コンソール版と一緒です。

gRPC Bidirectional streaming RPC

        public static void DoBiStreamTest(int jobCount)
        {
            var channel = new Channel(Host, Port, ChannelCredentials.Insecure);
            var client = new DataManager.DataManagerClient(channel);

            using (var call = client.BiStreamTest())
            {
                // Get ResponseMessage
                var responseTask = Task.Run(async () =>
                {
                    while (await call.ResponseStream.MoveNext(CancellationToken.None))
                    {
                        var res = call.ResponseStream.Current;
                        // レスポンス受取後、特に何もしない
                    }
                });

                // Send RequestMessage
                Task.Run(async () => {
                    for (int i = 0; i < jobCount; i++)
                    {
                        var req = new RequestMessage { Content = "TestContent" + i };
                        await call.RequestStream.WriteAsync(req);
                    }
                }).Wait();
                call.RequestStream.CompleteAsync().Wait();

                responseTask.Wait();
            }

            channel.ShutdownAsync().Wait();
        }

REST API (フォーマット: Protocol Buffers)

        public static IEnumerator DoRestApiTest(int jobCount, Action callback = null)
        {
            for (int i = 0; i < jobCount; i++)
            {
                var url = string.Format("{0}?content=TestContent{1}", Url, i);
                using (var req = UnityWebRequest.Get(url))
                {
                    yield return req.SendWebRequest();

                    if (req.isHttpError || req.isNetworkError)
                    {
                        throw new Exception(string.Format("DoRestApiTest. Error:{0}", req.error));
                    }

                    var bytes = req.downloadHandler.data;
                    var res = ResponseMessage.Parser.ParseFrom(bytes);
                    // レスポンス受取後、特に何もしない
                }
            }

            // 処理終了後にコールバックで戻す
            if (callback != null)
                callback();
        }

コンソール版と異なり、UnityWebRequestを使用するにように変更します。

REST API (フォーマット: Json)

        public static IEnumerator DoRestApiJsonTest(int jobCount, Action callback = null)
        {
            var enc = Encoding.UTF8;
            for (int i = 0; i < jobCount; i++)
            {
                var url = string.Format("{0}/json?content=TestContent{1}", Url, i);
                using (var req = UnityWebRequest.Get(url))
                {
                    yield return req.SendWebRequest();

                    if (req.isHttpError || req.isNetworkError)
                    {
                        throw new Exception(string.Format("DoRestApiJsonTest. Error:{0}", req.error));
                    }

                    var bytes = req.downloadHandler.data;
                    var res = JsonUtility.FromJson<ResponseJsonMessage>(enc.GetString(bytes));
                    // レスポンス受取後、特に何もしない
                }
            }

            // 処理終了後にコールバックで戻す
            if (callback != null)
                callback();
        }

コンソール版と異なり、UnityWebRequest、JsonUtilityを使用するにように変更します。

パフォーマンス

スループット

秒間10000件ちかくの通信をスマフォアプリから実行するのは考えづらいので、
10件から100件あたりのスループット(経過時間/ジョブカウント)の値を下記にします。

  • 10〜100件のスループットの結果 スクリーンショット 2018-04-02 21.54.44.png
    • 結果、gRPCのパフォーマンスが高い
    • ちなみにgRPCのUnary RPC、Bidirectional streaming RPCの10000件の計測結果はコンソールアプリの結果とほぼ同値

その他のパフォーマンス

Unity上(スマフォ版のビルド)で動かす際には、CPU負荷・メモリが気になるところと思いますので計測しました。
下記、JobCount=1にて使用した結果です。

  • Unity Profilerにて
    • Used Memoryは4パターンともほぼ同じ
    • コネクション接続時のCPU負荷はREST APIよりgRPCを使用した場合のほうが高くなる(特にアプリ起動後の初期コネクション)

最後に

コンソール版の検証同様、gRPCの通信速度はかなり速いです。メモリにおいてもREST APIと比べて問題はなさそうです。
gRPCの初期コネクション時に処理落ちが起きないかが唯一の懸念点ですので、実際のアプリ作成時には工夫が必要かもしれません。

補足

Unityのビルド方法に関してはこちら