静态变量存储在非静态方法调用中的哪个位置?即在CalculateTotalStatic()
内,我们正在增加静态成员MyStaticVariableHolder.Total
,并且还在for循环中将变量i
与MyStaticVariableHolder.TotalArray.Length
进行比较。
另一方面,在此方法CalculateTotalLocal()
的另一个版本中,我们使用方法中声明的局部变量来执行上述操作。
在CalculateTotalLocal
期间,堆栈上会放置另外两个变量,这些变量将在堆栈本身(localTotal
和localLength
)上保存它们的值。在CalculateTotalStatic
的情况下会发生什么?它每次都从堆中访问静态变量吗?此外,CalculateTotalLocal
比CalculateTotalStatic
快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;
}
}
我也在查看这个链接 - http://www.dotnetperls.com/local-variable-field-optimization以供参考,但我没有像他们那样获得尽可能多的性能提升。
答案 0 :(得分:5)
静态变量存储在非静态方法调用中的哪个位置?
我认为你的意思是:从静态变量中获取一个值,以便对其执行某些计算。因此,复制是由值。副本的存储空间是什么?
在IL的级别,它是对评估堆栈的。运行时具有广泛的自由度,可以根据需要重新评估评估堆栈。当前线程或寄存器的堆栈都可能。
我注意到在某些情况下可以将副本发送到堆位置。
在CalculateTotalStatic的情况下会发生什么?它每次都从堆中访问静态变量吗?
等等,谁说他们一开始就在堆里?没有要求将静态变量存储在垃圾收集堆上。他们的记忆力不会被收集,为什么他们应该在堆上呢? (变量引用的数组当然在堆上。)
让我们改写一下。
在CalculateTotalStatic的情况下会发生什么?它是否每次都对静态变量进行全新访问?
这个问题仍然没有问题。再说一遍。
运行时是否需要每次都从变量中进行新的提取?
没有。允许运行时执行任何在单线程程序中不可见的优化。 (这是一个轻微的过度陈述;有一些优化它不会执行。我不会列出它们是什么。)内化这个事实。在单线程程序中,除非移动,否则一切都是稳定的。在多线程程序中,除非保持静止,否则一切都在移动。即使在后者为真的世界中,也允许运行时假设前者。
此外,CalculateTotalLocal比CalculateTotalStatic快10%。这种性能改进的正确原因是什么?
我不知道。检查生成的代码,看看有什么区别。
可以(根据C#规范)通过兼容的C#compiler / JIT以与本地变量相同的方式优化静态变量访问吗?
绝对是的。这完全属于&#34;任何在单线程程序中都不可见的优化&#34;。
此外:运行时不仅允许在其认为合适时重新排序读取和写入。它还允许不同的线程观察不一致的世界。如果一个线程观察到正在读取和写入的特定时间序列的变量,则允许另一个线程观察到完全不同的读写交错。
此外:永远不要忘记运行时我的意思是运行时所依赖的任何东西,例如 CPU 。请记住,CPU允许广泛的自由度,以便在他们认为合适的情况下重新排序读取和写入。只是因为你正在查看一些x86代码,这些代码明确地将内存中的位置读入寄存器,这对于读取实际进入内存的时间绝对没有任何。该内存位置可能已经在缓存中,主内存可能已经写在另一个线程上,有效地向后移动读取。
此外:挥发性并不一定有帮助。对于那些认为可以正确预测在强内存模型上只对静态变量进行易失访问的程序行为的人,我建议您阅读http://blog.coverity.com/2014/03/26/reordering-optimizations/并查看是否可以正确推导出允许的读写序列。请记住,这是在强大的内存模型上;现在想想弱内存模型可能会有多难!
当您放弃跨线程共享内存的标准模式和实践时,您处于深水中。不要去那里。
答案 1 :(得分:4)
这是关于框架类的线程安全性的样板MSDN注释:
线程安全
此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。不保证任何实例成员都是线程安全的。
一般来说,马毛的负荷并不应该被认为是准确的,它是复制/粘贴文档。然而,对于静态变量有一个保证,并且包含这个blurb的唯一原因,.NET内存模型 承诺对其他线程可以看到对这样一个变量的赋值。
这意味着抖动无法优化此类变量的读取或写入。底层加载或存储指令不能延迟,也不能重新排序。这不是免费的。
对于局部变量没有这样的问题,抖动很难保证这个变量永远不会被另一个线程看到。这允许抖动注册变量,将其存储在CPU寄存器中。处理器上可用的最快内存。
请记住心理模型不正确,“堆叠上会有两个额外的变量”不准确。当您在没有附加调试器的情况下运行程序的Release版本时,这两个变量实际上都将存储在CPU寄存器中。你得到的性能提升可以在这样的测试中测量。
静态变量的存储通常是从加载器堆(AppDomain实现细节)中分配的。这不会影响程序的性能,抖动知道变量的确切地址,因为它是分配存储的地址。
答案 2 :(得分:1)
除非您的static
字段被声明为volatile
,否则.NET JIT可以自由地执行您所做的相同优化,并且每个主要版本都会变得更好,这可以解释差异从不知何故的日期“Dot Net Perls”文章。
我对静态版本10%差异的最佳猜测是static int[] TotalArray = new int[100]
初始化程序,它可以并且很可能 对MyStaticVariableHolder static
成员访问施加一些开销以确保它只运行一次而且只在它实际需要之前运行一次。