【问题标题】:Performance difference passing lambda versus separate method传递 lambda 与单独方法的性能差异
【发布时间】:2016-05-25 07:09:05
【问题描述】:

以下缓存帮助程序采用键名和创建者函数。如果缓存中已经包含命名对象,则返回,否则调用创建者创建对象,将其粘贴到缓存中并返回新对象。

public static T GetObject<T>(String name, Func<T> creator) { ... }

显然,创建者函数将被调用一次(第一次),而其他 999 次将被忽略。

对性能有何影响?如果这只是一个方法指针,那么我认为这将是非常微不足道的 - 每次只需将一个指针推入堆栈 - 但是如果 lambda 很昂贵,可能是一个带状态的闭包 - 每次我们调用时实际完成了多少工作this 在对象已经创建之后(将 lambda 推入堆栈只是为了被忽略)。

是否有替代策略?

【问题讨论】:

  • 有趣的问题,但我怀疑你是否会注意到任何性能下降,无论你的 lambda 有多“昂贵”。
  • 一个 lambda 实际上是语法糖。编译器实际上会自动生成一个方法/类(取决于闭包)。到达 IL 后,代码几乎相同。
  • 如果您想了解性能影响,请分析代码并找出答案。您已经编写了代码。运行它。
  • @Aron 啊,这就是困扰我的部分。因此,如果它捕获了本地变量,那么编译器会将其生成为一个类,传递类方法,唯一的区别就是将许多本地变量添加到自动生成的类中。
  • @Etherman 实际上没有。 C# 编译器的一个怪癖是它总是捕获this,以提高性能。这会导致来自 R# 的“访问修改的闭包”警告。它们的主要区别在于闭包需要实例化。

标签: c# performance lambda


【解决方案1】:

让我们先给你的方法一个实现,然后我们可以看看幕后发生了什么。

private static Dictionary<string, object> _LUT;

public static T GetObject<T>(String name, Func<T> creator)
{
    object obj;
    if (_LUT.TryGetValue(name, out obj))
    {
        return (T)obj;
    }
    T ret = creator();
    _LUT.Add(name, ret);
    return ret;
}

public string GetString(string name)
{
    return GetObject<string>(name, () => "Foo");
}

GetObject(...) 的说明:

IL_0000: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0005: ldarg.0
IL_0006: ldloca.s System.Object (0)
IL_0008: callvirt Boolean TryGetValue(System.String, System.Object ByRef)
IL_000d: brfalse.s IL_0016
IL_000f: ldloc.0
IL_0010: unbox.any T
IL_0015: ret
IL_0016: ldarg.1
IL_0017: callvirt T Invoke()
IL_001c: stloc.1
IL_001d: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0022: ldarg.0
IL_0023: ldloc.1
IL_0024: box T
IL_0029: callvirt Void Add(System.String, System.Object)
IL_002e: ldloc.1
IL_002f: ret

GetString(...) 指令:

IL_0000: ldarg.1
IL_0001: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0006: brtrue.s IL_0019
IL_0008: ldnull
IL_0009: ldftn System.String <GetString>b__0()
IL_000f: newobj Void .ctor(System.Object, IntPtr)
IL_0014: stsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0019: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_001e: call System.String GetObject[String](System.String, System.Func`1[System.String])
IL_0023: ret

如您所见,您的 lambda 被存储在 System.Func1[System.String] 类型的静态字段中

通过引用 (Why are delegates reference types?) 传递给您的方法,并在需要时调用 System.Func1[System.String].Invoke(),因此方法的大小应该没有区别。

关于是否存在替代方案的问题:

如果这是一次性功能,您可以尝试System.Lazy,如果您正在寻找一般缓存支持,我建议您查看:.NET 4 Caching Support

【讨论】:

    猜你喜欢
    • 2011-02-22
    • 2021-11-19
    • 2017-12-29
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    • 2011-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多