Производительность скомпилированных лямбда-выражений C #

Рассмотрим следующие простые манипуляции с коллекцией:

static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = x.Where(i => i % 2 == 0).Where(i => i > 5);

Теперь давайте использовать выражения. Следующий код примерно эквивалентен:

static void UsingLambda() {
    Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5);
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambda(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda: {0}", tn - t0);
}

Но я хочу построить выражение на лету, поэтому вот новый тест:

static void UsingCompiledExpression() {
    var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0));
    var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5));
    var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
    var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX));
    var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);

    var c3 = f.Compile();

    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = c3(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda compiled: {0}", tn - t0);
}

Конечно, это не совсем так, как указано выше, поэтому, если честно, я немного изменю первый:

static void UsingLambdaCombined() {
    Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0);
    Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5);
    Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l));
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambdaCombined(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda combined: {0}", tn - t0);
}

Теперь пришли результаты для MAX = 100000, VS2008, отладка включена:

Using lambda compiled: 23437500
Using lambda:           1250000
Using lambda combined:  1406250

И с отключенной отладкой:

Using lambda compiled: 21718750
Using lambda:            937500
Using lambda combined:  1093750

Удивление . Скомпилированное выражение примерно в 17 раз медленнее, чем другие альтернативы. Теперь вот вопросы:

  1. Сравниваю ли я неэквивалентные выражения?
  2. Существует ли механизм, позволяющий .NET "оптимизировать" скомпилированное выражение?
  3. Как программно выразить один и тот же цепной вызов l.Where(i => i % 2 == 0).Where(i => i > 5);?

Еще немного статистики. Visual Studio 2010, отладка включена, оптимизации выключены:

Using lambda:           1093974
Using lambda compiled: 15315636
Using lambda combined:   781410

Отладка включена, оптимизации включены:

Using lambda:            781305
Using lambda compiled: 15469839
Using lambda combined:   468783

Отладка выключена, оптимизация включена:

Using lambda:            625020
Using lambda compiled: 14687970
Using lambda combined:   468765

Новый сюрприз. Переключение с VS2008 (C # 3) на VS2010 (C # 4) приводит к UsingLambdaCombined быстрее, чем родная лямбда.


Хорошо, я нашел способ улучшить производительность лямбда-компиляции более чем на порядок. Вот подсказка; после запуска профилировщика 92% времени тратится на:

System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)

Хмммм ... Почему он создает новый делегат на каждой итерации? Я не уверен, но решение следует в отдельном посте.

90 голосов | спросил Hugo Sereno Ferreira 6 PMpWed, 06 Apr 2011 18:45:55 +040045Wednesday 2011, 18:45:55

0 ответов


Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132