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

本文关键字:性能 内存 变量 静态 局部变量 | 更新日期: 2023-09-27 18:36:48

非静态方法调用中的静态变量存储在哪里? 即在 CalculateTotalStatic() 内部,我们正在递增静态成员MyStaticVariableHolder.Total,并将变量i与 for 循环中的MyStaticVariableHolder.TotalArray.Length进行比较。

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

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

编辑 - 我想我不是很清楚 - 为此道歉。

我想说的是:

是否可以(根据 C# 规范)通过兼容的 C# 编译器/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;
    }
}

我也在查看此链接 - http://www.dotnetperls.com/local-variable-field-optimization 供参考,但我没有实现他们获得的那么多的性能改进。

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

静态变量存储在非静态方法调用中的什么位置?

我的意思是:从静态变量中获取值以对其执行一些计算。因此,将复制该值。副本制作到什么存储?

在 IL 级别,它被制作到评估堆栈。 运行时具有广泛的自由度,可以随心所欲地调整评估堆栈。 当前线程的堆栈或寄存器都可能。

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

在 CalculateTotalStatic 的情况下会发生什么?它是否每次都访问堆中的静态变量?

等等,谁说他们首先在堆上?不需要将静态变量存储在垃圾回收堆上。他们的记忆不会被收集,那么他们为什么要在堆上呢? (变量引用的数组当然在堆上。

让我们重新措辞。

在 CalculateTotalStatic 的情况下会发生什么?它是否每次都对静态变量进行全新访问?

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

运行时是否需要每次都从变量中重新获取?

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

此外,CalculateTotalLocal 比 CalculateTotalStatic 快 10%。这种性能改进的正确原因是什么?

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

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

绝对是的。这完全属于"在单线程程序中不可见的任何优化"。

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

此外:永远不要忘记,我所说的运行时也是指运行时所依赖的任何东西,比如 CPU。 请记住,允许 CPU 在他们认为合适的情况下对读取和写入进行重新排序。仅仅因为您正在查看一些 x86 代码,这些代码显然将内存中的位置读入寄存器,这与读取实际进入内存的时间完全无关。该内存位置可能已经在缓存中,并且主内存可能已经写入另一个线程,从而有效地将读取时间向后移动。

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

当你放弃跨线程共享内存的标准模式和做法时,你就处于深水区。不要去那里。

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

线程安全

此类型的任何公共静态(在 Visual Basic 中共享)成员都是线程安全的。不保证任何实例成员都是线程安全的。

一般来说,这是一堆马毛,不应该假设是准确的,它是复制/粘贴文档。 但是,您可以获得静态变量的一个保证,并且包含此简介的唯一原因是.NET内存模型确实承诺对此类变量的赋值可以对其他线程可见。

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

局部变量没有这样的问题,抖动有一个硬保证,这个变量永远不会被另一个线程看到。 这允许抖动寄存变量,并将其存储在CPU寄存器中。 处理器上可用的最快内存类型。

请记住,心智模型不正确,"堆栈上将放置两个额外的变量"是不准确的。 实际上,当您在没有附加调试器的情况下运行程序的发布版本时,这两个变量都将存储在 CPU 寄存器中。 您获得的性能改进在这样的测试中是可以衡量的。

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

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

我对静态版本 10% 差异的最佳猜测是 static int[] TotalArray = new int[100] 初始值设定项,它可以并且很可能确实会对MyStaticVariableHolder static成员访问施加一些开销,以确保它只运行一次,并且在实际需要之前只运行一次。