※ 「[C#][.NET][Roslyn] メタプログラミング入門 - 応用編 - オブジェクトの文字列変換のメタプログラミング (Roslyn 編)」の続き。
オブジェクトの文字列変換のメタプログラミング
ここまで、8通りの文字列変換メソッドを見てきた。
- メソッドの動的生成を行わない方法
- ToString() - ToString() をオーバーライドした静的なメソッド
- ToStringByReflection() - リフレクションによる動的なメソッド
- Reflection.Emit によってメソッドを動的生成
- ToStringByEmit初期型() - キャッシュ無し
- ToStringByEmit改() - キャッシュ有り
- 式木によってメソッドを動的生成
- ToStringByExpressionExtensions初期型() - キャッシュ無し
- ToStringByExpressionExtensions改() - キャッシュ有り
- Roslyn によってメソッドを動的生成
- ToStringByRoslyn初期型() - キャッシュ無し
- ToStringByRoslyn改() - キャッシュ有り
これらの方法について実行速度を比較していこう。
静的なメソッドに比べ、リフレクションによる動的なメソッドでは実行速度が低下する筈だ。また、メソッドの動的生成自体は遅いが、キャッシュがうまく効けば、リフレクションよりも速度面で有利な筈だ。
「Add メソッドのパフォーマンスの比較」や「メソッド呼び出しのパフォーマンスの比較」で行ったように、実行速度を測ってみよう。
先ず、パフォーマンス測定用のクラスを用意する。
using System; using System.Diagnostics; using System.Linq.Expressions; public static class パフォーマンステスター { public static void テスト(Expression<Action> 処理式, int 回数, Action<string> output) { // 処理でなく処理式として受け取っているのは、文字列として出力する為 var 処理 = 処理式.Compile(); var 時間 = 計測(処理, 回数).TotalMilliseconds; // 回数分の処理に掛かったミリ秒数 // 一回当たり何秒掛かったかを出力 output(string.Format("{0,64}: {1,8:F}/{2} 秒", 処理式.Body.ToString(), 時間, 回数 * 1000)); } static TimeSpan 計測(Action 処理, int 回数) { var stopwatch = new Stopwatch(); // 時間計測用 stopwatch.Start(); 回数.回(処理); stopwatch.Stop(); return stopwatch.Elapsed; } static void 回(this int @this, Action 処理) { for (var カウンター = 0; カウンター < @this; カウンター++) 処理(); } }
テスト用のクラスは、これまでと同じ Book クラスだ。
// テスト用のクラス public sealed class Book { public string Title { get; set; } public int Price { get; set; } }
パフォーマンスの測定
では、計ってみよう。
次のプログラムを走らせてみる。
using System; using System.Linq.Expressions; using System.Reflection; static class Program { static void Main() { パフォーマンステスト(); } static void パフォーマンステスト() { Console.WriteLine("【{0}】", MethodBase.GetCurrentMethod().Name); // メソッド名を表示 var book = new Book { Title = "メタプログラミング C#", Price = 3800 }; パフォーマンステスト(() => book.ToString ()); パフォーマンステスト(() => book.ToStringByReflection ()); パフォーマンステスト(() => book.ToStringByEmit初期型 ()); パフォーマンステスト(() => book.ToStringByEmit改 ()); パフォーマンステスト(() => book.ToStringByExpression初期型()); パフォーマンステスト(() => book.ToStringByExpression改 ()); パフォーマンステスト(() => book.ToStringByRoslyn初期型 ()); パフォーマンステスト(() => book.ToStringByRoslyn改 ()); } static void パフォーマンステスト(Expression<Action> 処理式) { const int 回数 = 1000; パフォーマンステスター.テスト(処理式, 回数, Console.WriteLine); } }
次のようになった。勿論、動作環境などによって結果は異なる。
【パフォーマンステスト】 value(Program+<>c__DisplayClass1).book.ToString(): 1.11/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByReflection(): 20.06/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByEmit初期型(): 716.75/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByEmit改(): 4.84/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByExpression初期型(): 466.80/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByExpression改(): 2.65/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByRoslyn初期型(): 4311.85/1000000 秒 value(Program+<>c__DisplayClass1).book.ToStringByRoslyn改(): 6.02/1000000 秒
速い順に並べてみよう。
順位 | 方法 | 時間 (マイクロ秒) |
---|---|---|
1 | ToString() - ToString() をオーバーライドした静的なメソッド | 1.11 |
2 | ToStringByExpressionExtensions改() - キャッシュ有り | 2.65 |
3 | ToStringByEmit改() - キャッシュ有り | 4.84 |
4 | ToStringByRoslyn改() - キャッシュ有り | 6.02 |
5 | ToStringByReflection() - リフレクションによる動的なメソッド | 20.06 |
6 | ToStringByExpressionExtensions初期型() - キャッシュ無し | 466.80 |
7 | ToStringByEmit初期型() - キャッシュ無し | 716.75 |
8 | ToStringByRoslyn初期型() - キャッシュ無し | 4311.85 |
ほぼ予想通りの結果となった。
静的なメソッドが最速だ。メソッドの動的生成はかなり遅い。キャッシュを行うことでこれらを大きく改善でき、場合によるが、リフレクションに対しても速度上のメリットがある。
まとめ
今回は、メタプログラミングによる文字列変換のまとめとして、パフォーマンスの比較を行った。