在C#中,将成员变量复制到本地堆栈变量是否可以提高性能?

Nic*_*ell 9 c# performance

我经常编写将成员变量复制到本地堆栈变量的代码,相信它会通过删除在访问成员变量时必须发生的指针解引用来提高性能.

这有效吗?

例如

public class Manager {
    private readonly Constraint[] mConstraints;

    public void DoSomethingPossiblyFaster() 
    {
        var constraints = mConstraints;
        for (var i = 0; i < constraints.Length; i++) 
        {
            var constraint = constraints[i];
            // Do something with it
        }
    }

    public void DoSomethingPossiblySlower() 
    {
        for (var i = 0; i < mConstraints.Length; i++) 
        {
            var constraint = mConstraints[i];
            // Do something with it
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

我的想法是DoSomethingPossiblyFaster实际上比DoSomethingPossiblySlower更快.

我知道这几乎是一个微观优化,但有一个确定的答案是有用的.

编辑 只是为此添加一点背景.我们的应用程序必须处理来自电信网络的大量数据,对于我们的一些服务器,这种方法可能每天被称为大约10亿次.我的观点是每一点都有帮助,有时我想做的就是给编译器一些提示.

Jon*_*eet 15

哪个更具可读性?这通常应该是您的主要激励因素.你甚至需要使用for循环而不是foreach

由于mConstraintsreadonly可能想到的JIT编译器为你做这一点-但实际上,你在循环做什么?这种重要性的可能性非常小.我几乎总是选择第二种方法只是为了可读性 - 而且我更愿意foreach在可能的地方.JIT编译器是否优化了这种情况将在很大程度上取决于JIT本身 - 它可能因版本,体系结构,甚至方法的大小或其他因素而异.可以有没有"明确的"答案在这里,因为它总是可能的替代JIT可以优化不同.

如果你认为自己处于一个真正重要的角落,你应该对它进行基准测试 - 尽可能使用尽可能真实的数据.只有这样才能将代码从最易读的形式改变.如果你"经常"编写这样的代码,你似乎不太可能给自己任何好处.

即使可读性差异相对较小,我也会说它仍然存在且显着 - 但我当然希望性能差异可以忽略不计.

  • -1.这是一个解决问题的答案.想知道x是否应该比y快,这是一个合理的问题.当差异非常小时,基准测试可能会很嘈杂.OP可能会问这个问题,因为他/她想要一个更好的心理模型来了解计算机是如何工作的,而不是直接的实用价值. (3认同)
  • +1,即使你是唯一一个编码这个东西的人,在离开它之后的几个月内回到它并尝试再次理解这些东西lol赌你不能,就像你不能读你自己的笔迹. (2认同)

dsi*_*cha 5

如果编译器/ JIT尚未为您执行此操作或进行类似的优化(如果这是很大的话),DoSomethingPossiblyFaster则应比快DoSomethingPossiblySlower。解释原因的最好方法是看一下C#代码到普通C的粗略翻译。

调用非静态成员函数时,会将隐藏的指针this传递给该函数。您将大致具有以下内容,因为它与问题无关(或者Manager为简单起见,将其密封),所以忽略了虚函数派发:

struct Manager {
    Constraint* mConstraints;
    int mLength;
}

void DoSomethingPossiblyFaster(Manager* this) {
    Constraint* constraints = this->mConstraints;
    int length = this->mLength;


    for (int i = 0; i < length; i++) 
    {
        Constraint constraint = constraints[i];
        // Do something with it
    }
 }

void DoSomethingPossiblySlower() 
{
    for (int i = 0; i < this->mLength; i++) 
    {
        Constraint constraint = (this->mConstraints)[i];
        // Do something with it
    }
}
Run Code Online (Sandbox Code Playgroud)

所不同的是在DoSomethingPossiblyFastermConstraints住在堆栈上和访问只需要指针间接的一个层,因为它在从栈指针的固定偏移。在中DoSomethingPossiblySlower,如果编译器错过了优化机会,则存在额外的指针间接寻址。编译器必须从堆栈指针this读取固定偏移量this以进行访问,然后从get 读取固定偏移量mConstraints

有两种可能的优化方法可以抵消此结果:

  1. 编译器可以完全执行您手动执行的操作并缓存mConstraints在堆栈中。

  2. 编译器可以将其存储this在寄存器中,以便在每次取消循环引用之前都不需要从堆栈中获取它。这意味着,获取mConstraintsthis或从堆栈基本上是相同的操作:从一个指针这是已经在寄存器中的固定偏移的单个解引用。