c# - 由于缓存委托(delegate),C#编译器的行为异常

标签 c# clr compiler-optimization

假设我有以下程序:

static void SomeMethod(Func<int, int> otherMethod)
{
    otherMethod(1);
}

static int OtherMethod(int x)
{
    return x;
}

static void Main(string[] args)
{
    SomeMethod(OtherMethod);
    SomeMethod(x => OtherMethod(x));
    SomeMethod(x => OtherMethod(x));
}

我无法理解编译的IL代码(它使用了太多额外的代码)。以下是简化版:
class C
{
    public static C c;
    public static Func<int, int> foo;
    public static Func<int, int> foo1;
    static C()
    {
        c = new C();
    }
    C(){}
    public int b(int x)
    {
        return OtherMethod(x);
    }
    public int b1(int x)
    {
        return OtherMethod(x);
    }
}

static void Main()
{
    SomeMethod(new Func<int, int>(OtherMethod));
    if (C.foo != null)
        SomeMethod(C.foo)
    else
    {
        C.foo = new Func<int, int>(c, C.b)
        SomeMethod(C.foo);
    }
    if (C.foo1 != null)
        SomeMethod(C.foo1)
    else
    {
        C.foo1 = new Func<int, int>(c, C.b1)
        SomeMethod(C.foo1);
    }
}

为什么编译器不创建静态相等方法?相等意味着他们有相同的代码

最佳答案

你的问题是:为什么编译器没有意识到

SomeMethod(x => OtherMethod(x));
SomeMethod(x => OtherMethod(x));

都是一样的
if ( delegate is not created ) 
  create the delegate and stash it away
SomeMethod( the delegate );
SomeMethod( the delegate );

是吗?好吧,让我用几种方式回答这个问题。
首先,编译器允许进行优化吗?对。该规范指出,允许C编译器将两个执行完全相同操作的lambda生成一个委托。事实上,您可以看到它已经部分地进行了这种优化:它只创建了一次每个委托,并将其保存起来,这样在以后再次调用代码时就不必再创建它了。注意,在只调用一次代码的情况下,这是浪费内存。
其次,是否需要编译器进行缓存优化?不。规范要求编译器只允许进行优化,但不要求进行优化。
是否需要编译器来进行所需的优化?显然不是,因为它没有。它是允许的,也许编译器的未来版本会。编译器是开源的;如果您关心这个优化,可以编写它并提交一个pull请求。
第三,是否可以进行您想要的优化?对。编译器可以获取出现在同一方法中的所有lambda对,将它们编译为内部树格式,并进行树比较以查看它们是否具有相同的内容,然后为两者生成相同的静态备份字段。
所以现在我们有一个情况:编译器被允许进行一个特定的优化,但它没有。你问“为什么不?”这是一个很容易回答的问题:除非有人花费大量时间和精力:
精心设计优化:在什么样的条件下才是触发优化而不是触发优化?优化应该有多普遍?你建议他们发现类似的lambda尸体,但为什么要停在那里?您有两个相同的代码语句,为什么不为这些语句生成一次代码而不是两次呢?如果你有一组重复的陈述呢?这里有大量的设计工作要做。
特别是,设计的一个重要方面是:用户是否可以在保持代码可读性的同时,合理地“手动”进行优化。在这种情况下,是的,他们可以,很容易。只需将重复的lambda赋给一个变量,然后使用该变量。一个自动完成用户关心的事情的优化并不是一个非常有趣或引人注目的优化。
你的例子微不足道,而现实世界的代码则不然。你建议的设计对相同的嵌套lambdas做什么?等等。
优化是否会导致调试器中代码的行为“看起来很奇怪”?您可能已经注意到,当调试在启用优化的情况下编译的代码时,调试器的行为似乎很奇怪;这是因为在生成的代码和原始代码之间不再有清晰的映射。你的优化会让情况更糟吗?用户能接受吗?调试器需要注意优化吗?如果是,则必须更改调试器。在这种情况下,可能不会,但这些是你必须问和回答的问题。
让专家对设计进行评审;这会占用他们的时间,并可能导致设计的更改
估计一下优化的利弊——优化通常有隐藏的成本,就像我之前提到的内存泄漏一样。特别是,优化通常会排除其他可能更好的优化。
对这种优化在全球范围内的总节省量进行估计。优化是否真正影响了实际的代码?它会改变代码的正确性吗?世界上是否有任何生产代码会破坏这种优化,导致X公司的CTO打电话给微软的CTO要求修复?如果答案是肯定的,那么您可能不想进行这种优化。C不是玩具。数以百万计的人每天都依赖它的正确运作。
在编译时进行优化的估计负担是多少?编译不必在击键之间进行,但必须非常快。在编译器的公共代码路径中引入超线性算法的任何东西都将是不可接受的。你能实现你的优化使它在代码大小上是线性的吗?注意,我之前画的算法——比较所有对——在代码大小上是超线性的。(练习:在所有对lambdas上进行树比较的最坏情况渐近性能是什么?)
实际实现优化。我鼓励你这样做。
测试优化;它真的能产生更好的代码吗?什么标准?不改变任何度量的优化不是优化。
注册永久修复优化中的错误。
你想要的优化根本达不到标准。没有人会写这样的代码。如果他们这样做了,并且他们关心它复制了一个对象,他们可以很容易地自己修复它。因此,优化优化不存在的代码,以便获得“胜利”,即在程序将分配的数以百万计的对象中构建单个对象。不值得。
但同样,如果你认为是的话,那么继续实现它并提交一个pull请求。一定要提交我上面提到的调查结果,因为这些才是真正的工作所在。实现通常是在一个特性上花费的全部精力的最小部分;这就是为什么c是一种成功的语言。

关于c# - 由于缓存委托(delegate),C#编译器的行为异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41922512/

相关文章:

c# - 将导航属性映射到实例变量作为外键

c# - 线程内存泄漏

.net - 强制 GC 变坏的示例?

c++ - 获取 GCC 选项参数的值

opengl - GLSL 真的用统一的(不是每个顶点的)值做不必要的计算吗?

c# - 具有自定义延迟加载对象的自动映射器

c# - 如何将 json 转换为 C# 中的模型列表?

c# - .NET:典型的垃圾收集器开销是多少?

c# - 阅读有关 .NET 编程的 Java 书籍

c# - 在 VB.NET、C# 等中,一点规则是否会自动优化到代码中?