はじめに
今回はType
をKeyにとるDictionary
をStatic Type Caching
に変えて処理を高速化させる方法について紹介したいと思います。
Static Type Caching
については以下のスライドのp46~
触れられています。
www.slideshare.net
概要
高速化できるDictionary
の条件は、Type
がKey
な場合です。
// Static Type Cachingに置き換えられる対象Dictionary private static readonly Dictionary<Type, int> Dic = new() { {typeof(int), 1}, {typeof(float), 2}, {typeof(double), 3} };
Static Type Caching
に置き換えるには、Dictionary
のKey
をジェネリックにして、Value
をフィールドにします。
// Static Type Cachingに置き換えたもの // アプリケーションが終了するまでメモリに存在し続けてしまうことに注意 static class Cache<T> { public static int value; static Cache() { // Tに対してリフレクションで操作したりする場合は静的コンストラクタで一度だけ呼び出す(スレッドセーフで必ず一度しか呼ばれない) var t = typeof(T); if (t == typeof(int)) { value = 1; } if (t == typeof(float)) { value = 2; } if (t == typeof(double)) { value = 3; } } }
バイナリサイズを減らす方法
ジェネリックの型ごとに重複したコードがバイナリに含まれるので、コードサイズが大きい箇所はジェネリックの外に切り出した方が良いでしょう。
// Static Type Cachingを利用した場合 // アプリケーションが終了するまでメモリに存在し続けてしまうことに注意 static class Cache<T> { public static int value; static Cache() { // Tに対してリフレクションで操作したりする場合は静的コンストラクタで一度だけ呼び出す(スレッドセーフで必ず一度しか呼ばれない) value = CacheHelper.CreateValue(typeof(T)); } } private static class CacheHelper { // Cacheの中に記述するとジェネリックの数だけコードが含まれてしまうため、バイナリサイズが膨らんでしまうので切り出せるところは切り出したい // CacheのTが値型 or 参照型で違うらしい(要調査) public static int CreateValue(Type t) { if (t == typeof(int)) { return 1; } if (t == typeof(float)) { return 2; } if (t == typeof(double)) { return 3; } return 0; } }
実験
どれくらい高速化できるか実験してみます。
public class Program { public static void Main(string[] args) { BenchmarkRunner.Run<Test>(); } } public class Test { // Dictionaryを利用した場合 (<Type, 〇〇>) private static readonly Dictionary<Type, int> Dic = new() { {typeof(int), 1}, {typeof(float), 2}, {typeof(double), 3} }; // Static Type Cachingを利用した場合 (DictionaryのKeyがTypeのとき代替可能) // T毎にインスタンスが生成される // アプリケーションが終了するまでメモリに存在し続けてしまうことに注意 static class Cache<T> { public static int value; static Cache() { // Tに対してリフレクションで操作したりする場合は静的コンストラクタで一度だけ呼び出す(スレッドセーフで必ず一度しか呼ばれない) value = CacheHelper.CreateValue(typeof(T)); } } private static class CacheHelper { // Cacheの中に記述するとジェネリックの数だけコードが含まれてしまうため、バイナリサイズが膨らんでしまうので切り出せるところは切り出したい // CacheのTが値型 or 参照型でコードの含まれ方が違うらしい(要調査) public static int CreateValue(Type t) { if (t == typeof(int)) { return 1; } if (t == typeof(float)) { return 2; } if (t == typeof(double)) { return 3; } return 0; } } [Benchmark] public int UseDictionary() { return Dic[typeof(int)] + Dic[typeof(float)] + Dic[typeof(double)]; } [Benchmark] public int UseStaticTypeCaching() { return Cache<int>.value + Cache<float>.value + Cache<double>.value; } }
結果
Static Type Caching
を利用していた方がかなり高速化していました。(今回の実験では1000倍ほどの差が...)
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
UseDictionary | 21.8183 ns | 0.4515 ns | 0.7788 ns | 21.8598 ns |
UseStaticTypeCaching | 0.0184 ns | 0.0187 ns | 0.0175 ns | 0.0107 ns |