Выступление делегата и методика группы - PullRequest
4 голосов
/ 15 марта 2010

Здравствуйте, я исследовал снижение производительности создания объектов Cachedependency, поэтому я написал очень простую тестовую программу следующим образом:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Caching;

namespace Test
{
    internal class Program
    {
        private static readonly string[] keys = new[] {"Abc"};
        private static readonly int MaxIteration = 10000000;

        private static void Main(string[] args)
        {
            Debug.Print("first set");
            test7();
            test6();
            test5();
            test4();
            test3();
            test2();
            Debug.Print("second set");
            test2();
            test3();
            test4();
            test5();
            test6();
            test7();
        }

        private static void test2()
        {
            DateTime start = DateTime.Now;
            var list = new List<CacheDependency>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(new CacheDependency(null, keys));
            }

            Debug.Print("test2 Time: " + (DateTime.Now - start));
        }

        private static void test3()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => new CacheDependency(null, keys));
            }

            Debug.Print("test3 Time: " + (DateTime.Now - start));
        }

        private static void test4()
        {
            var p = new Program();
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(p.GetDep);
            }

            Debug.Print("test4 Time: " + (DateTime.Now - start));
        }

        private static void test5()
        {
            var p = new Program();
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => { return p.GetDep(); });
            }

            Debug.Print("test5 Time: " + (DateTime.Now - start));
        }

        private static void test6()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(GetDepSatic);
            }

            Debug.Print("test6 Time: " + (DateTime.Now - start));
        }

        private static void test7()
        {
            DateTime start = DateTime.Now;
            var list = new List<Func<CacheDependency>>();
            for (int i = 0; i < MaxIteration; i++)
            {
                list.Add(() => { return GetDepSatic(); });
            }

            Debug.Print("test7 Time: " + (DateTime.Now - start));
        }

        private CacheDependency GetDep()
        {
            return new CacheDependency(null, keys);
        }

        private static CacheDependency GetDepSatic()
        {
            return new CacheDependency(null, keys);
        }
    }
}

Но я не могу понять, почему эти результаты выглядят так:*

first set
test7 Time: 00:00:00.4840277
test6 Time: 00:00:02.2041261
test5 Time: 00:00:00.1910109
test4 Time: 00:00:03.1401796
test3 Time: 00:00:00.1820105
test2 Time: 00:00:08.5394884
second set
test2 Time: 00:00:07.7324423
test3 Time: 00:00:00.1830105
test4 Time: 00:00:02.3561347
test5 Time: 00:00:00.1750100
test6 Time: 00:00:03.2941884
test7 Time: 00:00:00.1850106

В частности:

  1. Почему test4 и test6 намного медленнее, чем их версия делегата?Я также заметил, что Resharper специально прокомментировал версию делегата, предложив изменить test5 и test7 на «Covert to method group».Что такое test4 и test6, но на самом деле они медленнее?
  2. Мне кажется, что при вызовах test4 и test6 разница в производительности отсутствует, разве статические вызовы не должны быть всегда быстрее?1013 *

Ответы [ 4 ]

3 голосов
/ 16 июня 2014

В тестах с группой методов (4,6) компилятор C # не кэширует объект делегата (Func). Это создает новое каждый раз. В 7 и 5 он кэширует объект Action в сгенерированный метод, который вызывает ваши методы. Таким образом, создание Funcs из групп методов очень медленное (так как выделение Heap), но вызов быстр, поскольку действие указывает непосредственно на ваш метод. И создание действий из лямбда-кода происходит быстро, так как Func кэшируется, но он указывает на сгенерированный метод, поэтому существует один ненужный вызов метода.

Помните, что не все лямбды могут быть кэшированы (замыкания нарушают эту логику)

2 голосов
/ 16 марта 2010

Я не слишком подробно изучил ваш код, но первым шагом было бы переключить вещи, чтобы использовать класс StopWatch вместо DateTime.Now и т. Д.

http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx

1 голос
/ 22 марта 2010

Это довольно интересно. Мне интересно, не приводят ли ваши миллионы списков записей к сбору мусора и искажают ваши результаты. Попробуйте изменить порядок вызова этих функций и посмотрите, что дадут вам результаты.

Другое дело, что JIT мог оптимизировать ваш код, чтобы не создавать лямбда-выражения каждый раз, а просто вставлять одно и то же значение снова и снова. Может быть стоит запустить ildasm и посмотреть, что на самом деле генерируется.

0 голосов
/ 29 ноября 2017

Почему test4 и test6 намного медленнее, чем их версия делегата? Я также заметил, что Resharper специально прокомментировал версию делегата, предложив изменить test5 и test7 на «Covert to method group». Что такое test4 и test6, но на самом деле они медленнее?

Вы получите большую подсказку, добавив

        Debug.Print(ReferenceEquals(list[0], list[1]) ? "same" : "different");

до конца каждого метода.

С версией делегата Func компилируется немного, как это было на самом деле:

var func = Func<CacheDependency> <>_hiddenfieldwithinvalidC#name;
if (func == null)
{
  <>_hiddenfieldwithinvalidC#name = func = () => p.GetDep();
}

Хотя с группой методов она компилируется почти так же, как:

func = new Func<CacheDependency>(p.GetDep());

Эта памятка часто делается для делегатов, созданных из лямбда-выражений, когда компилятор может определить, что это безопасно, но не для групп методов, передаваемых делегатам, и различия в производительности, которые вы видите, показывают, почему.

Мне кажется, что при вызовах test4 и test6 не наблюдается существенной разницы в производительности, разве статические вызовы не должны быть всегда быстрее?

Не обязательно. В то время как статический вызов имеет преимущество в меньшем количестве передаваемых аргументов (так как нет неявного аргумента this), это различие:

  1. Не много для начала.
  2. Может быть сброшено, если this не используется.
  3. Может быть оптимизировано так, что регистр с указателем this перед вызовом является регистром с указателем this после вызова, поэтому на самом деле не нужно ничего делать, чтобы получить его там.
  4. Эх, что-то еще. Я не утверждаю, что этот список является исчерпывающим.

На самом деле выигрыш в производительности от статики заключается в том, что если вы будете делать то, что естественно статично в методах экземпляра, вы можете получить чрезмерную передачу объектов, которые на самом деле не нужны и тратят время. Тем не менее, если вы делаете то, что является естественным экземпляром в статических методах, вы можете в конечном итоге хранить / извлекать и / или размещать и / или передавать объекты в аргументах, которые вам не понадобятся, и все будет так же плохо.

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