gpt4 book ai didi

c# - 编译的 C# lambda 表达式性能与叠层

转载 作者:可可西里 更新时间:2023-11-01 07:45:02 25 4
gpt4 key购买 nike

考虑这个类:

/// <summary>
/// Dummy implementation of a parser for the purpose of the test
/// </summary>
class Parser
{
public List<T> ReadList<T>(Func<T> readFunctor)
{
return Enumerable.Range(0, 10).Select(i => readFunctor()).ToList();
}

public int ReadInt32()
{
return 12;
}

public string ReadString()
{
return "string";
}
}

我尝试使用已编译的 lambda 表达式树生成以下调用:

Parser parser = new Parser();
List<int> list = parser.ReadList(parser.ReadInt32);

然而,性能并不完全相同......

class Program
{
private const int MAX = 1000000;

static void Main(string[] args)
{
DirectCall();
LambdaCall();
CompiledLambdaCall();
}

static void DirectCall()
{
Parser parser = new Parser();
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < MAX; i++)
{
List<int> list = parser.ReadList(parser.ReadInt32);
}
sw.Stop();
Console.WriteLine("Direct call: {0} ms", sw.ElapsedMilliseconds);
}

static void LambdaCall()
{
Parser parser = new Parser();
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < MAX; i++)
{
List<int> list = parser.ReadList(() => parser.ReadInt32());
}
sw.Stop();
Console.WriteLine("Lambda call: {0} ms", sw.ElapsedMilliseconds);
}

static void CompiledLambdaCall()
{
var parserParameter = Expression.Parameter(typeof(Parser), "parser");

var lambda = Expression.Lambda<Func<Parser, List<int>>>(
Expression.Call(
parserParameter,
typeof(Parser).GetMethod("ReadList").MakeGenericMethod(typeof(int)),
Expression.Lambda(
typeof(Func<int>),
Expression.Call(
parserParameter,
typeof(Parser).GetMethod("ReadInt32")))),
parserParameter);
Func<Parser, List<int>> func = lambda.Compile();

Parser parser = new Parser();
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < MAX; i++)
{
List<int> list = func(parser);
}
sw.Stop();
Console.WriteLine("Compiled lambda call: {0} ms", sw.ElapsedMilliseconds);
}
}

这些是我电脑上以毫秒为单位的结果:

Direct call:          647 ms
Lambda call: 641 ms
Compiled lambda call: 5861 ms

我不明白为什么编译后的 lambda 调用这么慢。

我忘了说我的测试是在启用“优化代码”选项的 Release模式下运行的。

更新:将基于DateTime.Now 的基准测试更改为Stopwatch

有谁知道如何调整 lambda 表达式以在已编译的 lambda 调用中获得更好的性能?

最佳答案

测试无效有两个原因:

DateTime.Now 不够准确,无法进行微基准测试。

改用 Stopwatch 类。当我这样做时,我得到以下结果(使用 MAX = 100000),以毫秒为单位:

Lambda call: 86.3196
Direct call: 74.057
Compiled lambda call: 814.2178

事实上,“直接调用”比“lambda 调用”更快,这是有道理的 - “直接调用”涉及调用委托(delegate),该委托(delegate)直接指向 Parser 对象上的方法. “lambda 调用”需要调用一个委托(delegate),该委托(delegate)引用编译器生成的闭包对象上的方法,该对象又调用 Parser 对象上的方法。这种额外的间接引入了一个小的减速带。


“编译的 lambda 调用”与“Lambda 调用”不同

“Lambda”看起来像这样:

() => parser.ReadInt32()

而“已编译的 lambda”看起来像这样:

parser => parser.ReadList(() => parser.ReadInt32())

其中有一个额外的步骤:为内部 lambda 创建嵌入式委托(delegate)。在紧密循环中,这是昂贵的。

编辑:

我继续检查“lambda”与“编译的 lambda”的 IL,并将它们反编译回“更简单的”C#(参见:Viewing the IL code generated from a compiled expression)。

对于“未编译”的 lambda,它看起来像这样:

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);
}

请注意,单个委托(delegate)创建一次并缓存。

而对于“已编译的 lambda”,它看起来像这样:

Func<Parser, List<int>> func = lambda.Compile();
Parser parser = new Parser();
for (int i = 0; i < 100000; i++)
{
func(parser);
}

委托(delegate)的目标是:

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));
}

请注意,虽然“外部”委托(delegate)只创建一次并缓存,但在循环的每次迭代中都会创建一个新的“内部”委托(delegate)。更不用说 object 数组和 StrongBox<T> 实例的其他分配。

关于c# - 编译的 C# lambda 表达式性能与叠层,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8051869/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com