【C#】リフレクションによる速度劣化の改善アプローチ


【C#】リフレクションによる速度劣化の改善アプローチ

はじめに

実運用で使用しているアプリがリフレクションを頻繁に使用しているので、処理速度を改善出来る余地があるかどうかサンプルプログラムを作成して確認しました。
改善ポイントはループ内でリフレクションを行っていた点をループ外で行うようにしました。

環境

  • Window10 64bit
  • .NET Framework 4.7.2
  • Windows アプリケーション

サンプルプログラムの概要

計測①ボタンは1000万回繰り返すループ内でリフレクションによるクラスの型取得とインスタンス化を行い、インスタンス化したクラスのメソッドを呼び出します。
ループを抜けた後は処理時間を計測②ボタンの隣のラベルに出力します。
計測①ボタンのコードは下記の通り。

Form1.cs
        /// <summary>
        /// 計測①:ループ内でクラスの型取得とインスタンス化を行い、メソッドを実行する
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            var sw = new Stopwatch();
            // 計測開始
            sw.Start();

            for (int i = 0; i < 10000000; i++)
            {
                // クラスの型取得
                Type cType = Type.GetType("UI_Module.TestFunc");

                if (cType != null)
                {
                    // クラスのメソッド取得
                    MethodInfo m = cType.GetMethod("Func1");

                    if (m != null)
                    {
                        // インスタンス化
                        object obj = Activator.CreateInstance(cType);
                        // メソッド実行
                        m.Invoke(obj, null);
                    }

                }

            }

            // 計測終了
            sw.Stop();

            // 結果表示
            var ts = sw.Elapsed;
            label_Msg.Text = string.Format(
                "計測①" + Environment.NewLine
                + "{0}秒{1}ミリ秒", ts.Seconds, ts.Milliseconds);
        }

計測②ボタンはループの外でクラスの型取得とインスタンス化を行い、ループ内ではインスタンス化したオブジェクトを使いまわしてメソッドを実行します。
計測②ボタンのコードは下記の通り。

Form1.cs
        /// <summary>
        /// 計測②:ループの外でクラスの型取得とインスタンス化を行い、ループ内ではメソッド実行のみ行う
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            var sw = new Stopwatch();
            // 計測開始
            sw.Start();

            // クラスの型取得
            Type cType = Type.GetType("UI_Module.TestFunc");
            MethodInfo m = null;
            object obj = null;

            if (cType != null)
            {
                // クラスのメソッド取得
                m = cType.GetMethod("Func1");

                if (m != null)
                {
                    // インスタンス化
                    obj = Activator.CreateInstance(cType);
                }

            }

            for (int i = 0; i < 10000000; i++)
            {
                if (m != null && obj != null) 
                {
                    // メソッド実行
                    m.Invoke(obj, null);
                }
            }

            // 計測終了
            sw.Stop();

            // 結果表示
            var ts = sw.Elapsed;
            label_Msg.Text = string.Format(
                "計測②" + Environment.NewLine
                + "{0}秒{1}ミリ秒", ts.Seconds, ts.Milliseconds);
        }
    }

結果

処理速度を比較した結果、計測②が速くなっていました。

計測① 56秒240ミリ
計測② 20秒694ミリ
改善率 63%

サンプルプログラムは1クラス1メソッドの構成のため、劇的に速くなることはありませんでしたが、実運用で使用しているアプリの場合はクラスやメソッドの数が膨大であるため、さらに効果が上がるのではないかと感じました。

終わりに

これまでにも処理速度改善のアプローチとしてループ内で行っている重い処理をループ外に移動することで処理速度の改善を図ってきた経験はありましたが、リフレクションにおいても実際にサンプルプログラムを作成してみて効果があることが実感できました。