方法是否System.Activator.CreateInstance(T)
存在性能问题(因为我怀疑它使用反射)足以阻止我们随意使用它?
5 回答
与往常一样,回答有关性能的问题的唯一正确方法是实际测量代码。
这是一个用于测试的示例LINQPad程序:
- 激活器.CreateInstance
- 新 T()
- 调用调用 new T() 的委托
与往常一样,对性能程序持保留态度,这里可能存在会影响结果的错误。
输出(计时值以毫秒为单位):
Test1 - Activator.CreateInstance<T>() 12342 Test2 - 新 T() 1119 测试 3 - 代表 1530 基线 578
请注意,上述时间适用于对象的 100.000.000(1 亿)次构造。对于您的程序来说,开销可能不是真正的问题。
谨慎的结论是,Activator.CreateInstance<T>
完成相同工作所花费的时间大约是 a 的 11 倍new T()
,而代表则大约需要 1.5 倍。请注意,这里的构造函数什么都不做,所以我只尝试测量不同方法的开销。
编辑:我添加了一个基线调用,它不构造对象,但会做其余的事情,并对其进行计时。以此为基准,看起来委托比简单的 new() 多花费 75% 的时间,而 Activator.CreateInstance 多花费大约 1100%。
但是,这是微优化。如果你真的需要这样做,并找出一些时间关键代码的最后一盎司性能,我会手动编写一个委托来代替使用,或者如果那是不可能的,即。您需要在运行时提供类型,我将使用 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# .NET 4.0 程序:
- 激活器.CreateInstance
- 新 T()
- 调用调用 new T() 的委托
- 通用新()
- 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
这是从 Lasse V. 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()
内部调用Activator.CreateInstance<T>()
又调用RuntimeType.CreateInstanceDefaultCtor(...)。因此,如果您有一个通用方法来创建 的新实例T
,那么它应该没关系,尽管有一个new()
约束和调用new T()
更具可读性。这是Jon Skeet 关于该主题的相关链接。
是的,实际上它存在性能问题(与 相比new()
),因为它Reflection
在您将参数传递给它(向类的构造函数发送参数)而不是使用默认构造函数(如下所示)时使用和静态编译器特别检查
//Too bad!!!
T someResult = (T)Activator.CreateInstance(
typeof(T),
//parameter
new object[] {id}
);
在我看来,是否使用它取决于两件事:
首先是您的应用程序类型,当然还有它的规模(以及典型的流量)
其次(更重要的是)如何以及在何处使用Activator.CreateInstance
方法,例如,如果您在每个带有一个或多个构造函数参数的请求上使用它(正如我提到的,使用构造函数参数几乎比不使用参数(默认构造函数)慢十分之一) ),您的应用程序的性能几乎会大幅下降,但是对于另一个实例,如果您使用它一次(例如在 application_start 上)并且没有构造函数参数,它几乎就像new
关键字一样
这是和之间new()
的详细基准比较Activator.CreateInstance
Type.GetInstance()