【问题标题】:Why is reflection faster than object Initialization?为什么反射比对象初始化快?
【发布时间】:2020-02-22 10:48:00
【问题描述】:
  1. 对不起,非计算机科学术语(自学)
  2. 抱歉,术语不正确(任何愿意编辑的人,请做,(自学))

我尽可能多地搜索,但我对所搜索内容的术语知识有限,但我找不到并回答我想要的问题。

我惊讶地发现通过Reflection 向对象填充数据比使用object Initialization 更快。

我为此目的制作了一个测试控制台应用程序。 首先是测试类,

class TestClass
    {
        public string PropertyOne { get; set; }
        public string PropertyTwo { get; set; }
        public string PropertyThree { get; set; }
        public string PropertyFour { get; set; }
        public string PropertyFive { get; set; }
        public string PropertySix { get; set; }
        public string PropertySeven { get; set; }
        public string PropertyEight { get; set; }
        public string PropertyNine { get; set; }
        public string PropertyTen { get; set; }
    }
}

然后是获取数据的方法, 首先是反射,

public static void ReflectionTest()
    {
        for (int i = 0; i < 10000000; i++)
        {
            TestClass testClass = new TestClass();
            Type type = testClass.GetType();
            PropertyInfo[] properties = type.GetProperties();
            foreach (var propertyInfo in properties)
            {
                switch (propertyInfo.Name)
                {
                    case nameof(testClass.PropertyOne):
                        propertyInfo.SetValue(testClass, "PropertyOne" + i);
                        break;
                    case nameof(testClass.PropertyTwo):
                        propertyInfo.SetValue(testClass, "PropertyTwo" + i);
                        break;
                    case nameof(testClass.PropertyThree):
                        propertyInfo.SetValue(testClass, "PropertyThree" + i);
                        break;
                    case nameof(testClass.PropertyFour):
                        propertyInfo.SetValue(testClass, "PropertyFour" + i);
                        break;
                    case nameof(testClass.PropertyFive):
                        propertyInfo.SetValue(testClass, "PropertyFive)" + i);
                        break;
                    case nameof(testClass.PropertySix):
                        propertyInfo.SetValue(testClass, "PropertySix" + i);
                        break;
                    case nameof(testClass.PropertySeven):
                        propertyInfo.SetValue(testClass, "PropertySeven" + i);
                        break;
                    case nameof(testClass.PropertyEight):
                        propertyInfo.SetValue(testClass, "PropertyEight" + i);
                        break;
                    case nameof(testClass.PropertyNine):
                        propertyInfo.SetValue(testClass, "PropertyNine" + i);
                        break;
                    case nameof(testClass.PropertyTen):
                        propertyInfo.SetValue(testClass, "PropertyTen" + i);
                        break;
                }                    
            }
            TestClasses.Add(testClass);
        }
    }

接下来是初始化对象,

public static void InitializationTest()
    {
        for (int i = 0; i < 10000000; i++)
        {
            TestClass testClass = new TestClass
            {
                PropertyOne = "PropertyOne" + i,
                PropertyTwo = "PropertyTwo" + i,
                PropertyThree = "PropertyThree" + i,
                PropertyFour = "PropertyFour" + i,
                PropertyFive = "PropertyFive)" + i,
                PropertySix = "PropertySix" + i,
                PropertySeven = "PropertySeven" + i,
                PropertyEight = "PropertyEight" + i,
                PropertyNine = "PropertyNine" + i,
                PropertyTen = "PropertyTen" + i
            };
            TestClasses.Add(testClass);
        }
    }

还有测试代码

static List<TestClass> TestClasses { get; set; } = new List<TestClass>();

    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        ReflectionTest();
        Console.WriteLine($"Reflection Test time: {sw.Elapsed}");
        sw.Reset();
        sw.Start();
        InitializationTest();
        Console.WriteLine($"Initialization Test time: {sw.Elapsed}");
        sw.Stop();
    }

通过对象初始化,使用此代码反射的速度提高了 20%。 这是什么原因?

编辑:在代码中添加TestClasses.Clear(); 表明“对象初始化”几乎快两倍。感谢您的回复和cmets。

【问题讨论】:

  • 您最好在运行InitializationTest() 之前将TestClasses 集合重置为空,否则您将包括调整非常大数组大小所花费的时间。当我注释掉 TestClasses.Add(testClass); (这真的不应该成为测试的一部分!)时,我的结果给出了大约 30 秒的反射和 8 秒的初始化(对于 RELEASE 构建),使初始化速度提高了 3 倍!
  • 性能测试是一门艺术,而不是一个大循环计数器的小事。
  • @MatthewWatson,有道理。我重做了代码,你是对的。

标签: c# reflection


【解决方案1】:

这样的测量没有意义 - 否则每个人都会使用反射而不是对象实例。

结果令人困惑的主要原因是所有项目都被添加到同一个列表中。以不同的顺序调用测试,InitializationTest 表现更好。

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();

    sw.Start();
    InitializationTest();
    Console.WriteLine($"Initialization Test time: {sw.Elapsed}");
    sw.Stop();
    sw.Reset();

    sw.Start();
    ReflectionTest();
    Console.WriteLine($"Reflection Test time: {sw.Elapsed}");
    sw.Stop();
    sw.Reset();

    Console.ReadLine();
}

正如 Matthew Watson 在评论中所说:“更好的是,根本不要使用列表作为计时的一部分。我们应该计时创建对象需要多长时间 - 而不是创建对象需要多长时间填写一个非常大的列表。”


反射测试包含不必要的成本:重复的GetProperties 调用,switch 中的测试条件

public static void ReflectionTest()
{
    PropertyInfo[] properties = typeof(TestClass).GetProperties();
    for (int i = 0; i < 10000000; i++)
    {
        TestClass testClass = new TestClass();
        foreach (var propertyInfo in properties)
        {
            propertyInfo.SetValue(testClass, propertyInfo.Name + i);
        }
        TestClasses.Add(testClass);
    }
}

【讨论】:

  • 或者更好的是,根本不要使用列表作为计时的一部分。我们应该计时创建一个对象需要多长时间 - 而不是填充一个非常大的列表需要多长时间。
  • @MatthewWatson,但该测量可能存在另一个隐藏问题:在某些时候,GC 可能会决定介入并清除数百万丢失的 TestClass 实例,这些实例不再被引用
  • 确实如此,但我保证创建一个如此庞大的列表会更加扭曲时间(正如我们通过实际时间所看到的那样)。
  • 请注意,清理数百万个未引用的实例相对便宜——GC 只需要访问被引用的对象,它不会访问未引用的对象。在这些对象仍然被引用的情况下执行 GC(因为它会用完空间来分配新对象)的成本要高得多。
  • UV 来自我,谢谢您的回答。问题需要两个赞成!!!
【解决方案2】:

基准测试很难。始终使用BenchmarkDotNet

我使用 BenchmarkDotNet 重写了您的测试,并得到了以下结果:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-3317U CPU 1.70GHz (Ivy Bridge), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.0.100-preview6-012264
  [Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.30373, CoreFX 4.700.19.30308), X64 RyuJIT
  DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.30373, CoreFX 4.700.19.30308), X64 RyuJIT
|     Method |        Mean |     Error |    StdDev |
|----------- |-------------|-----------|-----------|
|   Instance |    41.61 ns |  0.309 ns |  0.274 ns |
| Reflection | 3,321.40 ns | 28.379 ns | 25.157 ns |

这清楚地表明您的反射代码比使用对象初始化器慢大约 80 倍。

@ASh 的回答谈到了您的基准测试存在缺陷的原因,所以我不会在这里复制 - 请参阅他们的回答。

基准代码:

public class Benchmark
{
    [Benchmark]
    public TestClass Instance()
    {
        TestClass testClass = new TestClass
        {
            PropertyOne = "PropertyOne",
            PropertyTwo = "PropertyTwo",
            PropertyThree = "PropertyThree",
            PropertyFour = "PropertyFour",
            PropertyFive = "PropertyFive)",
            PropertySix = "PropertySix",
            PropertySeven = "PropertySeven",
            PropertyEight = "PropertyEight",
            PropertyNine = "PropertyNine",
            PropertyTen = "PropertyTen"
        };
        return testClass;
    }

    [Benchmark]
    public TestClass Reflection()
    {
        TestClass testClass = new TestClass();
        Type type = testClass.GetType();
        PropertyInfo[] properties = type.GetProperties();
        foreach (var propertyInfo in properties)
        {
            switch (propertyInfo.Name)
            {
                case nameof(testClass.PropertyOne) :
                    propertyInfo.SetValue(testClass, "PropertyOne");
                    break;
                case nameof(testClass.PropertyTwo) :
                    propertyInfo.SetValue(testClass, "PropertyTwo");
                    break;
                case nameof(testClass.PropertyThree) :
                    propertyInfo.SetValue(testClass, "PropertyThree");
                    break;
                case nameof(testClass.PropertyFour) :
                    propertyInfo.SetValue(testClass, "PropertyFour");
                    break;
                case nameof(testClass.PropertyFive) :
                    propertyInfo.SetValue(testClass, "PropertyFive");
                    break;
                case nameof(testClass.PropertySix) :
                    propertyInfo.SetValue(testClass, "PropertySix");
                    break;
                case nameof(testClass.PropertySeven) :
                    propertyInfo.SetValue(testClass, "PropertySeven");
                    break;
                case nameof(testClass.PropertyEight) :
                    propertyInfo.SetValue(testClass, "PropertyEight");
                    break;
                case nameof(testClass.PropertyNine) :
                    propertyInfo.SetValue(testClass, "PropertyNine");
                    break;
                case nameof(testClass.PropertyTen) :
                    propertyInfo.SetValue(testClass, "PropertyTen");
                    break;
            }
        }
        return testClass;
    }
}

class Program
{
    public static void Main()
    {
        var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
    }
}

【讨论】:

  • UV 来自我,谢谢您的回答。问题需要两个赞成!!!
猜你喜欢
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-30
  • 1970-01-01
  • 2017-09-14
  • 2012-06-13
相关资源
最近更新 更多