C# で数通りあるインスタンス作成方法の処理速度の比較です。
前提条件・環境
環境は以下の通り
- Visual Studio 2019 + .NET Core 3.1
- Windows10, Releaseビルドをコンソールから実行
動作検証
検証は BenchmarkDotNet を使用して検証は以下の通り実行します。
// Program.cs internal class Program { private static void Main(string[] args) { var summary = BenchmarkRunner.Run<CreateInstance1>(); // もしくは以下を使う //var summary = BenchmarkRunner.Run<CreateInstance2>(); } }
試験に使用するクラスの定義は以下の通りです。
// MyClass.cs public class MyClass { string _str; int _value; public MyClass() { } public MyClass(string str, int value) => (_str, _value) = (str, value); public override string ToString() => $"{_str}={_value}"; }
引数無しのインスタンス作成
引数無しの場合以下の方法でインスタンスを作成できます。
- Case1: newして作成
- Case2: ジェネリックで作成
- Case3: Activatorで作成(ジェネリックを使用)
- Case4: Activatorで作成(Type渡しを使用)
検証コード
[MemoryDiagnoser] [RankColumn] public class CreateInstance1 { [Benchmark] public void Case1() { _list1.Clear(); for (int i = 0; i < 1000; i++) { var obj = new MyClass(); _list1.Add(obj); } } List<MyClass> _list1 = new List<MyClass>(); [Benchmark] public void Case2() { _list2.Clear(); for (int i = 0; i < 1000; i++) { var obj = Create<MyClass>(); _list2.Add(obj); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T Create<T>() where T : class, new() => new T(); List<MyClass> _list2 = new List<MyClass>(); [Benchmark] public void Case3() { _list3.Clear(); for (int i = 0; i < 1000; i++) { var obj = Activator.CreateInstance<MyClass>(); _list3.Add(obj); } } List<MyClass> _list3 = new List<MyClass>(); [Benchmark] public void Case4() { _list4.Clear(); for (int i = 0; i < 1000; i++) { var obj = (MyClass)Activator.CreateInstance(_type); _list4.Add(obj); } } Type _type = typeof(MyClass); List<MyClass> _list4 = new List<MyClass>(); }
実行結果
| Method | Mean | Error | StdDev | Rank | Gen0 | Gen1 | Allocated | |------- |----------:|----------:|----------:|-----:|-------:|-------:|----------:| | Case1 | 5.246 us | 0.0726 us | 0.0607 us | 1 | 7.6447 | 0.0076 | 31.25 KB | | Case2 | 31.587 us | 0.2984 us | 0.2646 us | 2 | 7.6294 | - | 31.25 KB | | Case3 | 31.233 us | 0.4671 us | 0.4369 us | 2 | 7.6294 | - | 31.25 KB | | Case4 | 33.266 us | 0.5065 us | 0.4737 us | 3 | 7.6294 | 0.0305 | 31.25 KB |
- Case2~4 Case1に比べて6倍遅い
- Case4 は Case2,3 に比べて微妙に遅い
引数ありのインスタンス作成
引数ありの場合はジェネリックが使用できないので以下の 2通りのインスタンス作成方法があります。
- newして作成
- Activatorで作成
検証コード
[MemoryDiagnoser] [RankColumn] public class CreateInstance2 { [Benchmark] public void Case1() { _list1.Clear(); string str = "asdf"; int value = 100; for (int i = 0; i < 1000; i++) { var obj = new MyClass(str, value); _list1.Add(obj); } } List<MyClass> _list1 = new List<MyClass>(); [Benchmark] public void Case2() { _list2.Clear(); string str = "asdf"; int value = 100; for (int i = 0; i < 1000; i++) { var obj = (MyClass)Activator.CreateInstance(_type, str, value); _list2.Add(obj); } } Type _type = typeof(MyClass); List<MyClass> _list2 = new List<MyClass>(); }
実行結果
| Method | Mean | Error | StdDev | Rank | Gen0 | Gen1 | Allocated | |------- |-----------:|----------:|----------:|-----:|--------:|-------:|----------:| | Case1 | 6.012 us | 0.1196 us | 0.2575 us | 1 | 1.9073 | 0.0992 | 31.25 KB | | Case2 | 311.745 us | 5.9345 us | 6.5962 us | 2 | 26.3672 | 1.4648 | 437.5 KB |
- Case1 と 2で50倍以上速度差がある
かなり昔から Activator.CreateInstanceは 遅いと言われていました、実際かなり遅いので通常は使用することはないと思います。
引数無しのほうはまぁまぁの速度でしたが引数ありの CreateInstance はかなり厳しいですね。
なぜか Allocated の項目が引数なしに比べて 15倍も増えてます。