Тест недействителен по двум причинам:
DateTime.Now
недостаточно точен для коротких тестов на микро-бенчмаркинге.
Вместо этого используйте класс Stopwatch
. Когда я делаю это, я получаю следующие результаты (используя MAX = 100000) в миллисекундах:
Lambda call: 86.3196
Direct call: 74.057
Compiled lambda call: 814.2178
Действительно, «прямой вызов» быстрее, чем «лямбда-вызов», что имеет смысл - «прямой вызов» включает в себя вызовы делегата, который напрямую ссылается на метод Parser
объект. «Лямбда-вызов» требует вызова делегата, который ссылается на метод объекта замыкания, созданного компилятором, который, в свою очередь, вызывает метод объекта Parser
. Эта дополнительная косвенность вводит незначительное ускорение.
«Скомпилированный лямбда-вызов» не совпадает с «Лямбда-вызовом»
«Лямбда» выглядит так:
() => parser.ReadInt32()
тогда как «Скомпилированная лямбда» выглядит так:
parser => parser.ReadList(() => parser.ReadInt32())
Там есть дополнительный шаг: создать встроенный делегат для внутренней лямбды. В узком кругу это дорого.
EDIT
Я пошел дальше и проверил IL «лямбды» против «скомпилированной лямбды» и декомпилировал их обратно в «более простой» C # (см .: Просмотр кода IL, сгенерированного из скомпилированного выражения ).
Для "не скомпилированной" лямбды это выглядит так:
for (int i = 0; i < 100000; i++)
{
if (CS$<>9__CachedAnonymousMethodDelegate1 == null)
{
CS$<>9__CachedAnonymousMethodDelegate1 = new Func<int>(CS$<>8__locals3.<LambdaCall>b__0);
}
CS$<>8__locals3.parser.ReadList<int>(CS$<>9__CachedAnonymousMethodDelegate1);
}
Обратите внимание, что один делегат создается один раз и кэшируется.
Принимая во внимание, что для "скомпилированной лямбды" это выглядит так:
Func<Parser, List<int>> func = lambda.Compile();
Parser parser = new Parser();
for (int i = 0; i < 100000; i++)
{
func(parser);
}
Где цель делегата:
public static List<int> Foo(Parser parser)
{
object[] objArray = new object[] { new StrongBox<Parser>(parser) };
return ((StrongBox<Parser>) objArray[0]).Value.ReadList<int>
(new Func<int>(dyn_type.<ExpressionCompilerImplementationDetails>{1}lambda_method));
}
Обратите внимание, что хотя «внешний» делегат создается только один раз и кэшируется, новый «внутренний» делегат создается на каждой итерации цикла. Не говоря уже о других выделениях для массива object
и экземпляра StrongBox<T>
.