局部变量与静态变量内存和性能

Local variable vs static variable memory and performance

静态变量存储在非静态方法调用中的什么地方? IE。在 CalculateTotalStatic() 中,我们递增静态成员 MyStaticVariableHolder.Total 并且还在 for 循环中比较变量 iMyStaticVariableHolder.TotalArray.Length

另一方面,在该方法的另一个版本中 CalculateTotalLocal(),我们使用方法中声明的局部变量来执行上述两个操作。

CalculateTotalLocal 期间,将有两个额外的变量放在堆栈上,它们将在堆栈本身上保存它们的值(localTotallocalLength)。在 CalculateTotalStatic 的情况下会发生什么?它是否每次都从堆中访问静态变量?此外,CalculateTotalLocalCalculateTotalStatic 快 10%。这种性能改进的正确原因是什么?

编辑 - 我想我不是很清楚 - 对此深表歉意。

我想说的是:

是否可以(根据 C# 规范)通过兼容的 C# 以与局部变量相同的方式优化静态变量访问 compiler/JIT?

 class MyStaticVariableHolder
{
    public static int[] TotalArray = new int[100];
    public static int Total = 1;
}
class Trial
{
    public void CalculateTotalStatic()
    {
        for (int i = 0; i < MyStaticVariableHolder.TotalArray.Length; i++)
        {
            MyStaticVariableHolder.Total += i;
        }

    }

    public void CalculateTotalLocal()
    {
        int localTotal = MyStaticVariableHolder.Total;
        int[] localTotalArray = MyStaticVariableHolder.TotalArray;
        int localLength = MyStaticVariableHolder.TotalArray.Length;
        for (int i = 0; i < localLength; i++)
        {
            localTotal += i;
        }
        MyStaticVariableHolder.Total = localTotal;

    }

}

我也在看这个 link - http://www.dotnetperls.com/local-variable-field-optimization 作为参考,但我没有像他们那样获得那么多的性能改进。

除非您的 static 字段被声明为 volatile,否则 .NET JIT 可以自由地进行与您所做的相同的优化,并且每个主要版本都在这方面变得更好,这将解释与不知何故过时的 "Dot Net Perls" 文章的区别。

我对静态版本 10% 差异的最佳猜测是 static int[] TotalArray = new int[100] 初始化程序,它可以并且很可能 确实 MyStaticVariableHolder static 成员访问以确保它 运行 只有一次,并且在实际需要之前只有一次。

这是关于框架线程安全的样板 MSDN 注释class:

Thread Safety

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

这通常是一堆马羽毛,不应假设是准确的,它是 copy/paste 文档。然而,静态变量有一个保证,并且包含此简介的唯一原因是,.NET 内存模型 确实 承诺对此类变量的赋值对其他线程可见。

这意味着抖动无法优化对此类变量的读取或写入。底层加载或存储指令不能延迟,也不能重新排序。这不是免费的。

局部变量没有这样的问题,抖动很难保证这个变量永远不会对另一个线程可见。这允许抖动 注册 变量,将其存储在 CPU 寄存器中。处理器上可用的最快内存。

请记住心智模型不正确,"there will be two additional variables placed on the stack" 不准确。当您 运行 在没有附加调试器的情况下发布程序版本时,这两个变量实际上都将存储在 CPU 寄存器中。您获得的性能改进可以在这样的测试中衡量。

静态变量的存储通常从加载程序堆分配,这是一个 AppDomain 实现细节。这不会影响程序的性能,抖动知道变量的确切地址,因为它是分配存储的地址。

Where are static variables stored inside a non static method call?

我认为你的意思是:从静态变量中获取一个值,以便对其执行一些计算。因此,复制了该值。复制到什么存储?

在 IL 级别,它已进入评估堆栈。运行时有广泛的自由来具体化评估堆栈,但它喜欢。当前线程的栈,或者寄存器都有可能。

我注意到在某些情况下可以将副本复制到堆位置。

What happens in the case of CalculateTotalStatic? Does it access the static variables from the heap every time?

等等,谁说他们一开始就在堆上?不需要将静态变量存储在垃圾收集堆中。他们的记忆是不会被收集的,那他们为什么要在堆上呢? (变量指向的数组当然在堆上了。)

让我们换个说法。

What happens in the case of CalculateTotalStatic? Does it do a fresh access to the static variable every time?

问题仍然无法回答。再次改写。

Is the runtime required to do a fresh fetch from the variable every time?

没有。允许运行时进行任何在单线程程序中不可见的优化。 (这是一个轻微的夸大其词;有一些优化它不会执行。我不打算列出它们是什么。)将这个事实内化。在单线程程序中,除非移动,否则一切都是稳定的。在多线程程序中,一切都在移动,除非保持静止。即使在后者为真的世界中,运行时也可以假设前者。

Also, CalculateTotalLocal is 10% faster than CalculateTotalStatic. What is the correct reason for this performance improvement?

我不知道。检查生成的代码,看看有什么区别。

can (according to C# spec) static variable access be optimized the same way as local variable by compliant C# compiler/JIT?

绝对是的。这恰好属于 "any optimization that would be invisible in a single-threaded program".

此外:不仅允许运行时按其认为合适的方式重新排序读取和写入。还允许 不同的线程观察不一致的世界 。如果一个线程观察到正在读取和写入的变量的特定时间序列,则允许另一个线程观察到完全不同的读取和写入交错。

此外:永远不要忘记运行时我的意思是运行时所依赖的任何东西,比如CPU。记住,CPUs 被允许有很大的自由度来按照他们认为合适的方式重新排序读写。仅仅因为您正在查看一些 x86 代码,这些代码清楚地将内存中的某个位置读取到寄存器中,绝对与读取实际进入内存的时间没有任何关系。该内存位置可能已经在缓存中,并且主内存可能已经写入另一个线程,有效地及时向后移动读取。

此外:volatile 不一定有帮助。对于那些相信他们可以正确预测在 strong 内存模型上只对静态变量进行易失性访问的程序的行为的人,我鼓励您阅读 http://blog.coverity.com/2014/03/26/reordering-optimizations/ 看看是否您可以正确推断出允许的读写顺序。请记住,这是在 strong 内存模型上;现在想想在弱内存模型上事情会变得多么困难!

当您放弃跨线程共享内存的标准模式和实践时,您就陷入了困境。别去那里