スキップしてメイン コンテンツに移動

Xamarin.iOSならExpressionよりリフレクションのほうが早いかもという話

元ネタはこちら: うさ☆うさ日記 - [C#]Xamarin.iOSでLambdaExpression.Compile()が通る件


最初に断っておきますが結構雑なベンチマーク計測してます

そこで、本題の件に入る前に技術的背景。

C#でのメタプログラミングにはいろいろと方法があって、この話に関係するものでも3つあります。

  1. リフレクション
    • Javaとかにもある奴で、型名とかメソッド名を取得したり実行できる、取得するのも実行するのもとても遅い
  2. IL Emit
    • 動的にCILを生成することでプログラム実行中に処理を追加できたりする、生成はそれなりに遅いが、一度生成すれば実行するのは高速
  3. Expression
    • IL EmitはCILをC#コードから生成することになるので普通の人にはできない、それを式木という形でC#っぽいコードでできるようにしたもの、これも一度生成すれば実行するのは高速

※正確な表現にはなってないかもしれませんが、だいたいこんな感じです。

そこで、問題のXamarin.iOSですが、iOSでは動的コード生成が許されてません(それぐらいいいじゃないかと思うかもしれませんが林檎の方針なので従うしかないです)。

動的コード生成が許されないということは、動的にコードを生成してるIL Emitはできないということです。
これはMSのドキュメント: Xamarin.iOS の制限事項にも書かれていることです。

ということで、IL Emitをしているパフォーマンス重視のライブラリーとかは軒並みXamarin.iOSでは利用できないことになりますが、同じような仕組みのExpressionを使っているライブラリーはなぜか動きます。

はて、なぜ動くのだろう?と考えると式木でリフレクションっぽいことをするようにしてるんだろうなぁということは想像つきますね。

問題のベンチマーク

元ネタでは丁寧にベンチマークをとられていましたが、めんどくさいのでインスタンス生成だけ軽くどんなものか計測してみることにします。

        public async void BenchmarkAsync()
        {
            string time = await timerAsync();
            // Xamarin.FormsのLabel
            Text.Text = time;
        }

        private async Task<string> timerAsync()
        {
            return await Task.Run(() =>
            {
                var sb = new StringBuilder();
                var sw = new Stopwatch();
                ConstructorInfo constructorInfo = typeof(Data).GetConstructors().First();

                sw.Start();
                for (int i = 0; i < 1000; i++) CreateInstanceReflection<Data>(constructorInfo);
                sw.Stop();

                sb.AppendLine($"createReflection: {sw.Elapsed.Ticks}");

                sw.Restart();
                for (int i = 0; i < 1000; i++) CreateInstanceExpression<Data>(constructorInfo);
                sw.Stop();

                sb.AppendLine($"createExpression: {sw.Elapsed.Ticks}");

                Func<Data> createInstanceDelegateReflection = CreateInstanceReflection<Data>(constructorInfo);
                Func<Data> createInstanceDelegateExpression = CreateInstanceExpression<Data>(constructorInfo);

                sw.Restart();
                for (int i = 0; i < 1000; i++) createInstanceDelegateReflection();
                sw.Stop();

                sb.AppendLine($"callReflection: {sw.Elapsed.Ticks}");

                sw.Restart();
                for (int i = 0; i < 1000; i++) createInstanceDelegateExpression();
                sw.Stop();

                sb.AppendLine($"callExpression: {sw.Elapsed.Ticks}");

                return sb.ToString();
            });
        }

        public static Func<TTarget> CreateInstanceReflection<TTarget>(ConstructorInfo constructorInfo)
        {
            return () => (TTarget)Activator.CreateInstance(constructorInfo.DeclaringType);
        }

        public static Func<TTarget> CreateInstanceExpression<TTarget>(ConstructorInfo constructorInfo)
        {
            return Expression.Lambda<Func<TTarget>>(Expression.New(constructorInfo)).Compile();
        }

計測コードはこんな感じにすごいてきとーです。なので正確性は全くないです。計測時もDebugモードでしたし、iPhoneシミュレーター使ったりAndroidエミュレーター使ったりしましたし。iPhoneシミュレーターに関してはホストマシンがWindowsなのでMacbook Proにシミュレートしてもらいました。
なので、(当たり前ですが)OS間の速度比は出ませんしReleaseビルドでどうなるかもわかりません。ただ、どれぐらいのリフレクションとExpressionの速度比なんだろうなぁということの参考になる程度です。

そして、結果ですが(左からiPhoneシミュレーター, UWP, Androidエミュレーター)、見事にiOSだけExpressionで生成したデリゲートの実行が遅いです。というか数値的にはリフレクションより遅い?

すでにあるメソッドを呼び出すだけなら大差ないかもしれませんが、動的コード生成みたいな感じでExpressionでデリゲートを組み立てたりすると仕組み上とても遅くなるかもしれません(未確認)。

作成中のライブラリーではExpressionの利用は控えてFodyとかでAOPに逃げようかなと思います……(確実に逃げれるとは言っていない)

追記

実際のところは実機確認しろということですね、iOSは難しい…(確信)

追記の追記

実機で調べてもらった リフレクションのほうが早い可能性あるのは正解っぽい?

コメント

このブログの人気の投稿

Stellaris 2.0.1 植民スパムプレイ

さっそくですが、友達との3日がかりのマルチプレイ終わりました。

銀河設定は中サイズ(600)、リング2、中盤の危機75年早く、終盤の危機100年早く、技術・伝統コスト0.5倍、難易度普通って感じでした。

そして、自分のプレイングは機械帝国植民スパム、友達は内向き牧歌でした。
最終的な国力としては機械帝国植民スパムのほうが強く、序盤から中盤は内向き牧歌のほうが強かったです。

機械帝国植民スパム はい、前回も言いましたが2.0から植民スパムゲーです。2.0.2からは前哨地維持費がかかるようになり更なる植民スパムゲーにもなりそうです。
植民スパムするからには、まぁロボットか機械帝国って感じなんですが、今回はゲーム進行が速い設定なので機械製造速度アップにガン振り帝国にしました。生産はエネルギー重視で、直轄地に鉱物惑星・研究惑星があるような感じにしました。
それでも、機械帝国は序盤の成長が遅く、鉱物を大量に消費するので、周りの帝国との不可侵条約はかかせません。直轄地6惑星(母星・エネルギー、エネルギー、研究*3、鉱物)の発展が終われば、今度はセクターの開発です。 まぁ、セクターの開発なんか物資さえ送り込めば勝手にやってくれるのでいいですが、エネルギー重視の生産なので鉱物の面倒は見てやらないとダメです。
帝国所有惑星が20ぐらいになれば、まぁ中銀河(サイズ600)なら敵なしですかね。あとは軍拡するだけです。
中盤の危機と終盤の危機 前述の通り危機はかなり早く発生するようにしたのですが、なんと中盤の危機がいっこうに発生しませんでした。発生したのは終盤の危機に立ち向かってるとき…おい()
終盤の危機は機械帝国プレイだったのでもちろんのようにコンティンジェンシー、中盤の危機は機械の反乱でした。
中盤の危機に関しては機械帝国プレイなので自分はどうでもよかったのですが、終盤の危機はがんばらないとまずかったです。コンティンジェンシーなので機械の生産-40%補正がつくので、スペシャルプロジェクトで解除(自分の研究力500ぐらいでは50か月ぐらいでした)しない限り国力が持ちません。
コンティンジェンシーは艦隊戦力が300k+100k*2の集団が4つほど湧いて100k艦隊は時間とともに増え続けるので急がないといけませんが、解除さえすればあまり余る国力で殴り続けるだけでしたね。終盤の危機の間に…

Stellaris ver2.0 Apocalypse プレイ感想

タイトルの通り大型アプデ後での友人とのマルチプレイでのプレイ感想です。

変更内容に関してはこちらのサイトで日本語訳された内容が公開されています(本当に助かりました)
Simulationian.com - 「Stellaris」開発日記#105――2.0「チェリイ」パッチノート

使用した帝国について 細かい設定は覚えていませんが(確認するのもめんどくさい)、有機生命体で狂信権威・平和主義で国是にアップデート内容の生まれた生命(Life-Seeded)を選択しています。 狂信権威について アップデートにより帝国の領域を増やすには星系ごとに影響力(1回限りに支払い)を使い前哨地を建てていく必要がありました。そこで狂信権威を選択したのですが、プレイスタイルが悪いのか、よく奴隷が不満を抱えストライキ?を起こしてしまいました。これに関しては防衛設備を地上に建て防衛軍を増やすことで対処できますが、タイルを無駄に消費している感じがすごいあります。 影響力が毎月4増えるのは領域を増やすのに好都合でしたが、Life-Seededとの相性が微妙でした(後述)。 Life-Seededについて 自分の種族がガイア型惑星にしか住めなくなるが、母星がガイア型25でスタートします。使用感想としては初期ブーストはいいかな?って感じです。ただ、アップデート後の環境ではプレイスタイルを頑張らなければ弱いかなと。その理由としては、まずアップデート後は研究コスト補正にPOP数が関係なくなり領域星系数が関係あるようになりました。つまり領域が大きければ大きいほどコストが高くなるということ。星系の資源はプレイヤーではどうしようもならない固定資源のようなものですから、それに頼って領域を大きくし過ぎると研究コストがすごいことになってしまいます。植民地を確保して賄おうにもガイア型にしか住めないのでかなり増やしにくいです。 ガイア型にしか住めないというのは、有機生命体であってロボットなら他のタイプの惑星に植民できるというのであれば、Life-Seededはロボットプレイをするしかないのかもしれないですが、これに関しては未検証です。
プレイスタイルについて 今回自分がとったプレイスタイルは要塞でガン守りキメるガチ芋戦略です。アップデートで要塞関連が変わり結構使えるものになったらしいので。 防衛プラットフォームを上限まで…

Xperia XZs SOV35をAndroid 8.0にアップデートしてみた

はい、タイトルの通りですが02/9の夕方ごろ8.0へのアップデートが降ってきたので、メイン機であるXperia XZsの人柱アップデートをしました。後悔はありません(今のところ) アップデート中は昼寝していたのでどれほど時間がかかったのかはわかりませんが電池の減りは20%ぐらいでした。


PiPを試そうとアレコレやってみたのですがどうもYouTubeはYouTube Redに入ってないとダメみたいです。
いちおうGoogle Play Musicの定額には入ってるんですけどね…日本でもYouTube Red始まらないといくらVPNでアメリカ装ってYouTube Red使おうにもGoogle Play Musicの定額連携はされないみたいです。 いちおうPiPはGoogle Mapの経路案内を開始で試しましたが、経路案内なんか今までしたことなく……使うこともなさそうでした。ChromeとかでもPiPできそうでしたがYouTube Webのほうを開いて動画流しても上手くできず…どのサイトが対応してるかはわかりませんでした。
設定画面からPiP対応のアプリを見ることはできますが、これ、信用できないですねぇ~~~