System.Activator.CreateInstance(T)
メソッドには、(リフレクションを使用していると思われるため)パフォーマンスの問題があり、カジュアルに使用することを思いとどまらせるほど大きな問題がありますか?
5 に答える
いつものように、パフォーマンスに関する質問に答える唯一の正しい方法は、実際にコードを測定することです。
テストするLINQPadプログラムのサンプルは次のとおりです。
- Activator.CreateInstance
- 新しいT()
- new T()を呼び出すデリゲートを呼び出す
いつものように、一粒の塩でパフォーマンスプログラムを実行すると、結果を歪めるバグがここにある可能性があります。
出力(タイミング値はミリ秒単位):
Test1-Activator.CreateInstance <T>() 12342 Test2-新しいT() 1119 Test3-委任 1530 ベースライン 578
上記のタイミングは、オブジェクトの100.000.000(1億)の構造に対するものであることに注意してください。オーバーヘッドは、プログラムにとって実際の問題ではない可能性があります。
注意すべき結論はActivator.CreateInstance<T>
、同じ仕事をするのに約11倍の時間new T()
がかかり、代理人は約1.5倍の時間がかかるということです。ここでのコンストラクターは何もしないので、さまざまなメソッドのオーバーヘッドを測定しようとしただけであることに注意してください。
編集:オブジェクトを構築しないが、残りのことを行うベースライン呼び出しを追加し、それもタイミングを合わせました。これをベースラインとして使用すると、デリゲートは単純なnew()よりも75%長くかかり、Activator.CreateInstanceは約1100%長くかかるように見えます。
ただし、これはマイクロ最適化です。本当にこれを行う必要があり、タイムクリティカルなコードの最後の1オンスのパフォーマンスを調べる必要がある場合は、代わりに使用するデリゲートを手動でコーディングするか、それが不可能な場合は、つまり、実行時に型を指定する必要がある場合は、Reflection.Emitを使用してそのデリゲートを動的に生成します。
いずれにせよ、そしてここに私の本当の答えがあります:
パフォーマンスに問題がある場合は、最初にボトルネックがどこにあるかを確認してください。はい、上記のタイミングは、Activator.CreateInstanceが動的に構築されたデリゲートよりもオーバーヘッドが大きいことを示している可能性がありますが、このレベルの最適化に到達する前に(または取得する必要がある前に)コードベースで揚げる魚がはるかに大きい可能性があります。
そして、私が実際にあなたの具体的な質問に答えることを確認するために:いいえ、Activator.CreateInstanceの使用を思いとどまらせるつもりはありません。リフレクションを使用していることに注意してください。これにより、ボトルネックのプロファイリングリストの上位にある場合は、それについて何かできる可能性がありますが、リフレクションを使用しているからといって、それがボトルネックであるとは限りません。
プログラム:
void Main()
{
const int IterationCount = 100000000;
// warmup
Test1();
Test2();
Test3();
Test4();
// profile Activator.CreateInstance<T>()
Stopwatch sw = Stopwatch.StartNew();
for (int index = 0; index < IterationCount; index++)
Test1();
sw.Stop();
sw.ElapsedMilliseconds.Dump("Test1 - Activator.CreateInstance<T>()");
// profile new T()
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test2();
sw.Stop();
sw.ElapsedMilliseconds.Dump("Test2 - new T()");
// profile Delegate
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test3();
sw.Stop();
sw.ElapsedMilliseconds.Dump("Test3 - Delegate");
// profile Baseline
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test4();
sw.Stop();
sw.ElapsedMilliseconds.Dump("Baseline");
}
public void Test1()
{
var obj = Activator.CreateInstance<TestClass>();
GC.KeepAlive(obj);
}
public void Test2()
{
var obj = new TestClass();
GC.KeepAlive(obj);
}
static Func<TestClass> Create = delegate
{
return new TestClass();
};
public void Test3()
{
var obj = Create();
GC.KeepAlive(obj);
}
TestClass x = new TestClass();
public void Test4()
{
GC.KeepAlive(x);
}
public class TestClass
{
}
テストするサンプルC#.NET4.0プログラムは次のとおりです。
- Activator.CreateInstance
- 新しいT()
- new T()を呼び出すデリゲートを呼び出す
- ジェネリックnew()
- ジェネリックを使用したActivator.CreateInstance
- 汎用バインディングとデフォルト以外のバインディングを使用するActivator.CreateInstance(たとえば、内部コンストラクターを呼び出すため)
出力(タイミング値は、x86リリースビルドを備えた2014年の強力なマシンからのミリ秒単位です):
Test1 - Activator.CreateInstance<T>(): 8542
Test2 - new T() 1082
Test3 - Delegate 1214
Test4 - Generic new() 8759
Test5 - Generic activator 9166
Test6 - Generic activator with bindings 60772
Baseline 322
これはLasseV.Karlsenの回答から採用されていますが、重要なことにジェネリックが含まれています。バインディングを指定すると、ジェネリックを使用するActivatorの速度が6倍以上遅くなることに注意してください。
using System;
using System.Reflection;
using System.Diagnostics;
namespace ConsoleApplication1
{
public class TestClass
{
}
class Program
{
static void Main(string[] args)
{
const int IterationCount = 100000000;
// warmup
Test1();
Test2();
Test3();
Test4<TestClass>();
Test5<TestClass>();
Test6<TestClass>();
// profile Activator.CreateInstance<T>()
Stopwatch sw = Stopwatch.StartNew();
for (int index = 0; index < IterationCount; index++)
Test1();
sw.Stop();
Console.WriteLine("Test1 - Activator.CreateInstance<T>(): {0}", sw.ElapsedMilliseconds);
// profile new T()
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test2();
sw.Stop();
Console.WriteLine("Test2 - new T() {0}", sw.ElapsedMilliseconds);
// profile Delegate
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test3();
sw.Stop();
Console.WriteLine("Test3 - Delegate {0}", sw.ElapsedMilliseconds);
// profile generic new()
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test4<TestClass>();
sw.Stop();
Console.WriteLine("Test4 - Generic new() {0}", sw.ElapsedMilliseconds);
// generic Activator without bindings
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test5<TestClass>();
sw.Stop();
Console.WriteLine("Test5 - Generic activator {0}", sw.ElapsedMilliseconds);
// profile Activator with bindings
sw.Restart();
for (int index = 0; index < IterationCount; index++)
Test6<TestClass>();
sw.Stop();
Console.WriteLine("Test6 - Generic activator with bindings {0}", sw.ElapsedMilliseconds);
// profile Baseline
sw.Restart();
for (int index = 0; index < IterationCount; index++)
TestBaseline();
sw.Stop();
Console.WriteLine("Baseline {0}", sw.ElapsedMilliseconds);
}
public static void Test1()
{
var obj = Activator.CreateInstance<TestClass>();
GC.KeepAlive(obj);
}
public static void Test2()
{
var obj = new TestClass();
GC.KeepAlive(obj);
}
static Func<TestClass> Create = delegate
{
return new TestClass();
};
public static void Test3()
{
var obj = Create();
GC.KeepAlive(obj);
}
public static void Test4<T>() where T : new()
{
var obj = new T();
GC.KeepAlive(obj);
}
public static void Test5<T>()
{
var obj = ((T)Activator.CreateInstance(typeof(T)));
GC.KeepAlive(obj);
}
private const BindingFlags anyAccess = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
public static void Test6<T>()
{
var obj = ((T)Activator.CreateInstance(typeof(T), anyAccess, null, null, null));
GC.KeepAlive(obj);
}
static TestClass x = new TestClass();
public static void TestBaseline()
{
GC.KeepAlive(x);
}
}
}
ユースケースによって異なります。非常に高いパフォーマンスが必要で、多くのオブジェクトを作成している場合は、使用Activator.CreateInstance
が問題になる可能性があります。
しかし、ほとんどの場合、それは十分に高速であり、オブジェクトを作成するための非常に強力な方法です。
実際、ほとんどのIoCコンテナー/サービスロケーター/それらを呼び出すものはすべて、このメソッドを使用して、要求しているタイプのオブジェクトを作成します。
パフォーマンスが十分に良くないことが心配な場合は、アプリケーションのプロファイリングを行い、ボトルネックがあるかどうか、およびそれがどこにあるかを測定する必要があります。私の推測では、への呼び出しはActivator.CreateInstance
あなたの問題ではないでしょう。
はい、呼び出しの間にパフォーマンスの違いがあります
(MyClass)Activator.CreateInstance(typeof(MyClass));
と
new MyClass();
後者の方が速いです。ただし、速度の低下が十分に大きいかどうかを判断するのは、ドメイン次第です。ケースの90%では、それは問題ではありません。また、値型の場合、ボックス化解除Activator.CreateInstance
が含まれるため、これも遅くなることに注意してください。
しかし、ここに問題があります。ジェネリック型の場合、それらは似ています。new T()
内部的に呼び出し、 RuntimeType.CreateInstanceDefaultCtor(...)Activator.CreateInstance<T>()
を呼び出します。したがって、の新しいインスタンスを作成するためのジェネリックメソッドがある場合は、制約があり呼び出しがはるかに読みやすくなりますが、それは問題ではありません。JonSkeetによるこのテーマに関連するリンクは次のとおりです。T
new()
new T()
はい、実際には(と比較してnew()
)パフォーマンスの問題があります。これはReflection
、デフォルトのコンストラクター(以下のように)を使用する代わりに、パラメーターを渡す(クラスのコンストラクターにパラメーターを送信する)ときに静的コンパイラーが特別にチェックするためです。
//Too bad!!!
T someResult = (T)Activator.CreateInstance(
typeof(T),
//parameter
new object[] {id}
);
私の意見ではそれを使用するかどうかは2つのことに依存します:
最初にアプリケーションの種類、そしてもちろんそれは規模です(そしてそれは典型的なトラフィックです)
そして2番目に(そしてもっと重要なことに)メソッドを使用するActivator.CreateInstance
方法と場所、たとえば、1つ以上のコンストラクターパラメーターを使用するすべてのリクエストでメソッドを使用する場合(コンストラクターパラメーターを使用する場合は、パラメーターなしを使用する場合(デフォルトのコンストラクター)よりもほぼ10分の1遅くなります) )、アプリケーションのパフォーマンスはほぼ大幅に低下しますが、別の例では、一度使用すると(たとえば、application_startで)、コンストラクターパラメーターがないと、ほとんどnew
キーワードのように機能します。
new()
これは、Activator.CreateInstance
との間の詳細なベンチマーク比較です。Type.GetInstance()