Почему отражение быстрее, чем инициализация объекта? - PullRequest
3 голосов
/ 26 октября 2019
  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% быстрее при использовании объекта Initialization. В чем причина этого?

РЕДАКТИРОВАТЬ: добавление TestClasses.Clear(); в код показывает, что «инициализация объекта» почти в два раза быстрее. Спасибо за ответы и комментарии.

Ответы [ 2 ]

5 голосов
/ 26 октября 2019

Бенчмаркинг сложно. Всегда используйте 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);
    }
}
5 голосов
/ 26 октября 2019

Такие измерения не имеют смысла - иначе все будут использовать отражение вместо экземпляров объекта.

Основная причина, по которой результаты сбивают с толку, заключается в том, что все элементы добавляются в один и тот же список. Вызывайте тесты в другом порядке, и 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();
}

Как сказал в комментарии Мэтью Уотсон: «еще лучше, не используйте списки вообще как часть времени. Предполагается, что он рассчитывает время, необходимое для создания объекта, а не время, необходимое для заполнения очень большого списка. "


Тест отражения включал ненужные затраты: повторные вызовы 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...