Почему делегаты, генерируемые динамически из выражений, работают медленнее, чем жестко запрограммированные лямбды? - PullRequest
4 голосов
/ 03 августа 2010

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

Вот простая тестовая программа, иллюстрирующая ситуацию.Он просто получает 3 свойства объекта 1000000 раз:

static void Main()
{
    var foo = new Foo { A = 42, B = "Hello world", C = new DateTime(1970, 1, 1) };

    Func<Foo, int> getA;
    Func<Foo, string> getB;
    Func<Foo, DateTime> getC;

    // Using hard-coded lambdas
    getA = f => f.A;
    getB = f => f.B;
    getC = f => f.C;
    Console.WriteLine("Hard-coded: {0}", Test(foo, getA, getB, getC));

    // Using dynamically generated delegates
    ParameterExpression prm = Expression.Parameter(typeof(Foo), "foo");
    getA = Expression.Lambda<Func<Foo, int>>(Expression.Property(prm, "A"), prm).Compile();
    getB = Expression.Lambda<Func<Foo, string>>(Expression.Property(prm, "B"), prm).Compile();
    getC = Expression.Lambda<Func<Foo, DateTime>>(Expression.Property(prm, "C"), prm).Compile();
    Console.WriteLine("Generated:  {0}", Test(foo, getA, getB, getC));
}

const int N = 1000000;

static TimeSpan Test(Foo foo, Func<Foo, int> getA, Func<Foo, string> getB, Func<Foo, DateTime> getC)
{
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < N; i++)
    {
        getA(foo);
        getB(foo);
        getC(foo);
    }
    sw.Stop();
    return sw.Elapsed;
}

public class Foo
{
    public int A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
}

Я последовательно получаю результаты, показывающие, что жестко запрограммированные лямбды примерно в 6 раз быстрее:

Hard-coded: 00:00:00.0115959
Generated:  00:00:00.0735896

Hard-coded: 00:00:00.0113993
Generated:  00:00:00.0648543

Hard-coded: 00:00:00.0115280
Generated:  00:00:00.0611804

Может кто-нибудь объяснить эти результаты?Это из-за оптимизации компилятора?или JIT-оптимизации?

Спасибо за ваше понимание


РЕДАКТИРОВАТЬ: я запускал свои тесты с LINQPad, который компилируется с включенной оптимизацией.Когда я запускаю одни и те же тесты в VS с отключенной оптимизацией, я получаю примерно одинаковые результаты в обоих случаях.Таким образом, кажется, что компилятор просто вставлял доступ к свойству в жестко запрограммированные лямбды ...

Дополнительный вопрос: есть ли способ оптимизировать код, сгенерированный из деревьев выражений?

Ответы [ 2 ]

3 голосов
/ 03 августа 2010

Там что-то еще происходит.Тест критически зависит от выбранной цели платформы .NET.Я делаю воспроизводим результаты для .NET 4.0, но .NET 3.5 стабильно быстрее в сгенерированной версии, независимо от типа сборки.

Угадаю, что могло измениться в 4.0, чтобы сделать это такнамного медленнее, слишком сложно, это не просто код для анализа.И поддержка дерева выражений значительно изменилась.Я рекомендую вам оставить отзыв на connect.microsoft.com, ожидайте отклика «по замыслу».Надеюсь, с комментариями.

3 голосов
/ 03 августа 2010

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

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

...