C# JITter 能否优化重复的委托调用?

Can the C# JITter optimize repeated delegate calls?

在我当前的代码库中,复杂的 if 语句通常被委托调用所取代。由于代码的结构,在应用程序的过程中将多次调用同一个委托。例如,

class ExampleClass
{
    private delegate double ExampleDelegate(double x, double y);
    private ExampleDelegate _exampleMethod;
    private bool _condition1;

    ...

    public double ApiFunction(List<double> a, List<double> b, bool condition2)
    {
        if ((_condition1 && !condition2) || getCondition3())
        {
            _exampleMethod = adder;
        }
        else
        {
            _exampleMethod = subtracter;
        }

        double finalResult = 0;

        for (int i = 0; i < a.Count; i++)
        {
            finalResult += _exampleMethod(a[i], b[i]);
        }

        return finalResult;
    }

    private double adder(double a, double b)
    {
        return a + b;
    }

    private double subtracter(double a, double b)
    {
        return a - b;
    }
}

由于这里的性能是一个问题,我想知道 JITter 是否最终会意识到每次都会调用这些方法之一并内联或以其他方式优化调用。

那么,C# JITter 能否内联或以其他方式优化重复的委托调用

没有。委托调用始终是间接调用,并在运行时动态绑定。这发生在调用时,只有那时委托对象的值才是已知的。抖动在此之前运行,优化器无法改进它们,它不知道究竟要调用什么。

请注意它无法知道目标方法是实例方法还是静态方法,它假定是实例方法。如果重新调整调用堆栈是静态的,则调用存根需要完成额外的工作。对于 x64 代码来说,额外的工作量更大。代码片段中值得注意的是,静态方法通常更有意义,因此请注意这一点。

第一次调用是昂贵的,那是在创建调用存根并且需要对目标方法进行调整时。因为在这种情况下,抖动已经正确地猜测了方法的样子,存根只是一个简单的 JMP 指令。此后的任何调用都以正常速度运行,并且在 JMP 之外,不会比正常调用慢很多,尽管您无法像这些小方法通常那样从内联中获益。没有进行任何更改,.NET 没有任何类似热点编译器的东西,因为它不使用解释器。

您必须进行测量,请注意这是非常快的代码,因此简单的基准测试错误往往会造成伤害并且测量结果不会非常一致。但是只有当你发现你有一个实际的性能问题时才考虑花时间在它上面。这不常见,代表们不烂。