关于C#:在这种情况下,为什么JIT不使用只读标志进行优化?


Why does the JIT not use the readonly flag for optimization in this case?

我有一个由这个CachePage类的数组组成的调用站点缓存。缓存页有一个用于确定此页是否正确使用的令牌的readonly数组。

1
2
3
4
5
6
7
8
internal class CachePage
{
    internal CachePage(Token[] tokens)
    {
        this.tokens = tokens;
    }
    private readonly Token[] tokens;
    // other members don't matter...

为了简化问题,在类I上有几个CheckTokens方法,它们采用不同数量的参数,它们的日志都是这样的。

1
2
3
4
public bool CheckTokens(Token g0, Token g1, Token g2)
{
    return (g2 == tokens[2] && g1 == tokens[1] && g0 == tokens[0]);
}

我向后迭代数组的元素,只进行一次绑定检查。我大概是这么想的。当我查看反汇编的输出时,我实际上看到了每次比较,它实际上都在执行boundcheck。

但是,如果我这样改变方法,多余的边界检查将被消除。

1
2
3
4
5
public bool CheckTokens(Token g0, Token g1, Token g2)
{
    var t=tokens;
    return (g2 == t[2] && g1 == t[1] && g0 == t[0]);
}

为什么要增加额外的约束支票?readonly标志是否告诉jit不能对这个私有变量进行赋值?

这是一个低级缓存,因此确定这是否是正确路径花费的时间越少越好。

编辑:这是针对64位.NET 4.5的,使用了ryujit和普通jit。


我在实时交易的世界中工作,在试图从应用程序中榨取最后一点性能的同时,我研究了这个问题。

正如汉斯在问题评论中所说,有些乐观情绪是神经过敏者选择忽视的。其中之一是注册一个方法中多次读取的readonly成员变量。虽然从概念上讲,这似乎是一个简单的实现方法,但要认识到多次读取同一个成员变量并不容易。

简单地说,这是一种权衡,在绝大多数情况下,潜在的好处不值得为抖动付出代价。如果您的性能调优已经达到了微优化的程度,那么显式地启用抖动,通过手动将成员变量提升为局部变量来注册成员变量。这具有连锁效应,使抖动能够执行其他优化,例如消除边界检查。