System.Activator.CreateInstance(T)的性能问题是否足以阻止我们随便使用它?

Pac*_*ier 30 .net performance instantiation activator

System.Activator.CreateInstance(T)方法是否有性能问题(因为我怀疑它使用反射)大到足以阻止我们随便使用它?

ang*_*son 49

与往常一样,回答关于性能的问题的唯一正确方法是实际测量代码.

这是一个示例LINQPad程序,用于测试:

  • Activator.CreateInstance
  • 新T()
  • 调用一个调用new T()的委托

和往常一样,在性能程序中加入一些盐,这里可能存在错误导致结果偏差的错误.

输出(时间值以毫秒为单位):

Test1 - Activator.CreateInstance<T>() 
12342 

Test2 - new T() 
1119 

Test3 - Delegate 
1530 

Baseline 
578 

请注意,上述时间是针对对象的100.000.000(1亿)个构造.开销可能不是您的程序的真正问题.

值得注意的结论是,Activator.CreateInstance<T>与同样的工作相同的工作时间大约是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
{
}
Run Code Online (Sandbox Code Playgroud)

  • 有趣的是注意T Create <T>()其中T:new(){return new T()}和Activator.CreateInstance一样慢,所以在实例化泛型类时有一个巨大的惩罚:( (4认同)
  • 好吧,我不会说很大,因为它与其他成本有关.它比一个简单的新()更昂贵,但我怀疑它是一个问题,除了最极端的情况. (3认同)

JDi*_*teo 9

这是一个示例C#.NET 4.0程序,用于测试:

  • Activator.CreateInstance
  • 新T()
  • 调用一个调用new T()的委托
  • 通用新()
  • 使用泛型的Activator.CreateInstance
  • 使用泛型和非默认绑定的Activator.CreateInstance(例如调用内部构造函数)

输出(时间值以毫秒为单位,来自2014年x86发布版本的强劲机器):

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
Run Code Online (Sandbox Code Playgroud)

这是从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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然这主要是对Lasse V. Karlsen的优秀答案的反刍,但我认为C#对大多数人来说更具相关性,包括通用的新()比较非常重要. (2认同)

Run*_*tad 7

这取决于您的使用案例.如果您需要非常高的性能并且正在创建许多对象,那么使用Activator.CreateInstance可能是个问题.

但在大多数情况下,它足够快,它是一种非常强大的创建对象的方法.

实际上,大多数IoC容器/服务定位器/无论您调用它们,都使用此方法来创建您请求的类型的对象.

如果您担心性能不够好,那么您应该对应用程序进行概要分析,并测量是否存在瓶颈及其位置.我的猜测是打电话Activator.CreateInstance不是你的问题.


naw*_*fal 5

是的,呼叫之间存在性能差异

(MyClass)Activator.CreateInstance(typeof(MyClass));
Run Code Online (Sandbox Code Playgroud)

new MyClass();
Run Code Online (Sandbox Code Playgroud)

后者更快的地方.但是,确定速度下降是否足够大取决于您的域名.在90%的情况下,这不是问题.另请注意,对于值类型,Activator.CreateInstance由于涉及拆箱而再次变慢.

但这里有一个问题:对于泛型类型,它们是相似的.new T()内部调用Activator.CreateInstance<T>(),然后调用RuntimeType.CreateInstanceDefaultCtor(...).因此,如果你有一个通用的方法来创建新的实例T,那么它应该无关紧要,尽管有一个new()约束和调用new T()更具可读性.这是Jon Skeet关于这个主题的相关链接.