Сортировать по отражению быстрее, чем по члену? - PullRequest
0 голосов
/ 10 июня 2018

В .Net много предупреждений относительно скорости использования Reflection.Для сортировки сетки данных по столбцу Datapropertyname я создал короткую тестовую программу, которая показывает, что Linq Sort by Reflection быстрее, чем по Member.Я не уверен, все ли я сделал правильно, и прошу сообщество пересмотреть.

class Program {

    private static Random random = new Random();

    class TestClass {
        public string Name { get; set; }
        public int Number { get; set; }
    }


     static string RandomString(int length) {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        return new string(Enumerable.Repeat(chars, length)
          .Select(s => s[random.Next(s.Length)]).ToArray());
    }


    static void SortyByReflection(List<TestClass> testlst) {
        PropertyInfo prop = typeof(TestClass).GetProperties().Where(p => p.Name == "Name").FirstOrDefault();
        List<TestClass> sorted = testlst.OrderBy(o => prop.GetValue(o)).ToList();
    }

    static void SortByMember(List<TestClass> testlst) {
        List<TestClass> sorted = testlst.OrderBy(o => o.Name).ToList();
    }


    delegate void dRunner(List<TestClass> testlst);

    static long UsedTime(dRunner testDelegate, List<TestClass> testlst) {
        Stopwatch timer = new Stopwatch();
        timer.Start();
        testDelegate(testlst);
        timer.Stop();
        return timer.ElapsedTicks;
    }

    static void Main(string[] args) {
        // make a dry run to init the program
        SortByMember(new List<TestClass>());
        SortyByReflection(new List<TestClass>());


        List<int> lstSize = new List<int> { 100, 1000, 10000, 100000 };
        foreach (int count in lstSize) {
            // Init List
            List<TestClass> testlst = new List<TestClass>();
            for (int i = 0; i < count; i++) {
                testlst.Add(new TestClass { Name = RandomString(10), Number = i });
            }
            List<long> reflection = new List<long>();
            List<long> memberTime = new List<long>();
            for (int i = 0; i < 100; i++) {
                reflection.Add(UsedTime(SortyByReflection,testlst));
                memberTime.Add(UsedTime(SortByMember,testlst));
            }
            Console.WriteLine($"{reflection.Min()} / {reflection.Max()} / {reflection.Average()} Min/ Max / Average Ticks needed for Reflection {count} size");
            Console.WriteLine($"{memberTime.Min()} / {memberTime.Max()} / {memberTime.Average()} Min/ Max / Average Ticks needed for Member {count} size");
            Console.WriteLine(new string('-', 80));
        }
        Console.WriteLine("done");
        Console.ReadLine();
    }
    /*
     * Sample output
        425 / 1837 / 539,75 Min/ Max / Average Ticks needed for Reflection 100 size
        479 / 1265 / 605,14 Min/ Max / Average Ticks needed for Member 100 size
        --------------------------------------------------------------------------------
        6251 / 11819 / 7309,82 Min/ Max / Average Ticks needed for Reflection 1000 size
        7164 / 13369 / 8201,42 Min/ Max / Average Ticks needed for Member 1000 size
        --------------------------------------------------------------------------------
        76214 / 103169 / 82003,53 Min/ Max / Average Ticks needed for Reflection 10000 size
        86139 / 121152 / 93201,55 Min/ Max / Average Ticks needed for Member 10000 size
        --------------------------------------------------------------------------------
        1092454 / 1188244 / 1139228,26 Min/ Max / Average Ticks needed for Reflection 100000 size
        1225469 / 1353753 / 1280549,37 Min/ Max / Average Ticks needed for Member 100000 size
        --------------------------------------------------------------------------------
        done
    */
}

Среднее значение всегда примерно на 10% быстрее при использовании Reflection.

После внедрения нового (Освободите, запустите за пределами Visual Studio, используйте результат и измените vom Порядок по имени на Порядок по номеру мир снова находится в балансе.

Новые числа, как и предполагалось:

/*
97 / 1266 / 129,67 Min/ Max / Average Ticks needed for Reflection 100 size
19 / 265 / 28,34 Min/ Max / Average Ticks needed for Member 100 size
---------------------------------------------------------------------------
1064 / 2369 / 1357,42 Min/ Max / Average Ticks needed for Reflection 1000 size
218 / 598 / 290,43 Min/ Max / Average Ticks needed for Member 1000 size
---------------------------------------------------------------------------
12407 / 27326 / 15779,35 Min/ Max / Average Ticks needed for Reflection 10000 size
2703 / 5100 / 3366,52 Min/ Max / Average Ticks needed for Member 10000 size
---------------------------------------------------------------------------
147184 / 198677 / 160490,02 Min/ Max / Average Ticks needed for Reflection 100000 size
35333 / 45620 / 38493,46 Min/ Max / Average Ticks needed for Member 100000 size
-------------------------------------------------------------------------
*/

А для файлов вот новый исходный код

  class Program {

    private static Random random = new Random();

    class TestClass {
        public string Name { get; set; }
        public int Number { get; set; }
    }


     static string RandomString(int length) {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        return new string(Enumerable.Repeat(chars, length)
          .Select(s => s[random.Next(s.Length)]).ToArray());
    }


    static List<TestClass> SortyByReflection(List<TestClass> testlst) {
        PropertyInfo prop = typeof(TestClass).GetProperties().Where(p => p.Name == "Number").FirstOrDefault();
        List<TestClass> sorted = testlst.OrderBy(o => prop.GetValue(o)).ToList();
        return sorted;
    }

    static  List<TestClass> SortByMember(List<TestClass> testlst) {
        List<TestClass> sorted = testlst.OrderBy(o => o.Number).ToList();
        return sorted;
    }


    delegate List<TestClass> dRunner(List<TestClass> testlst);

    static List<TestClass> UsedTime(dRunner testDelegate, List<TestClass> testlst,out long time) {
        time = 0;
        Stopwatch timer = new Stopwatch();
        timer.Start();
        var x = testDelegate(testlst);
        timer.Stop();
        time = timer.ElapsedTicks;
        return x;
    }

    static void Main(string[] args) {


        // make a dry run to init the program
        SortByMember(new List<TestClass>());
        SortyByReflection(new List<TestClass>());


        List<int> lstSize = new List<int> { 100, 1000, 10000, 100000 };
        foreach (int count in lstSize) {
            // Init List
            List<TestClass> testlst = new List<TestClass>();
            for (int i = 0; i < count; i++) {
                testlst.Add(new TestClass { Name = RandomString(10), Number = i });
            }
            List<long> reflection = new List<long>();
            List<long> memberTime = new List<long>();
            for (int i = 0; i < 100; i++) {
                foreach (var lst in UsedTime(SortyByReflection,testlst,out long time)){
                    var tmp = lst.Name;
                    tmp += lst.Number.ToString();
                    reflection.Add(time);
                }
                foreach (var lst in UsedTime(SortByMember, testlst, out long time)) {
                    var tmp = lst.Name;
                    tmp += lst.Number.ToString();
                    memberTime.Add(time);
                }

            }
            Console.WriteLine($"{reflection.Min()} / {reflection.Max()} / {reflection.Average()} Min/ Max / Average Ticks needed for Reflection {count} size");
            Console.WriteLine($"{memberTime.Min()} / {memberTime.Max()} / {memberTime.Average()} Min/ Max / Average Ticks needed for Member {count} size");
            Console.WriteLine(new string('-', 80));
        }
        Console.WriteLine("done");
        Console.ReadLine();
    }
    /*
        97 / 1266 / 129,67 Min/ Max / Average Ticks needed for Reflection 100 size
        19 / 265 / 28,34 Min/ Max / Average Ticks needed for Member 100 size
        --------------------------------------------------------------------------------
        1064 / 2369 / 1357,42 Min/ Max / Average Ticks needed for Reflection 1000 size
        218 / 598 / 290,43 Min/ Max / Average Ticks needed for Member 1000 size
        --------------------------------------------------------------------------------
        12407 / 27326 / 15779,35 Min/ Max / Average Ticks needed for Reflection 10000 size
        2703 / 5100 / 3366,52 Min/ Max / Average Ticks needed for Member 10000 size
        --------------------------------------------------------------------------------
        147184 / 198677 / 160490,02 Min/ Max / Average Ticks needed for Reflection 100000 size
        35333 / 45620 / 38493,46 Min/ Max / Average Ticks needed for Member 100000 size
        --------------------------------------------------------------------------------
    */
}

1 Ответ

0 голосов
/ 11 июня 2018

В первый раз, когда я увидел сообщение, я не доверял ему, как другим комментаторам.

Но после выполнения моих собственных тестов, устраняющих потенциальные влияния GC и JIT, я могу подтвердить, что утверждения верны -метод SortyByReflection действительно в среднем быстрее, чем SortByMember.

Однако это никак не связано с отражением!

В конце концов, метод Enumerable.OrderBy делает O (N) вызовов селектора (т. Е. Отражение или прямой доступ к свойству, которые вы пытаетесь измерить) и среднее O (N * Log2 (N)) сравнений , следовательно, времяСложность определяется реализацией операции сравнения.

И здесь возникает разница.Метод отражения использует Comparer<object>.Default, в то время как другой метод использует Comparer<string>.Default.И удивительно, по какой-то неизвестной причине, первая быстрее, чем вторая на string с.Я не проверял другие типы данных (скорее всего, для int и других типов значений картина будет противоположной из-за бокса, включенного в первый случай), но это относится к string.Я проверил справочный источник для реализаций компаратора и у меня есть идеи, что может быть его причиной, но это выходит за рамки текущего вопроса.

Важно то, что OrderBy не подходит длятестирование отражения против производительности прямого доступа.Если вы измените первый селектор метода на o => (string)prop.GetValue(o) и оставите второй как есть, или измените второй селектор на o => (object)o.Name и оставите первый как есть, запуск теста покажет вам, что прямой доступ быстрее ожидаемого отражения, но не столько из-за доминирования времени сравнения, особенно для больших N.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...