局部变量与静态变量内存和性能
Local variable vs static variable memory and performance
静态变量存储在非静态方法调用中的什么地方? IE。在 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;
}
}
我也在看这个 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 内存模型上;现在想想在弱内存模型上事情会变得多么困难!
当您放弃跨线程共享内存的标准模式和实践时,您就陷入了困境。别去那里
静态变量存储在非静态方法调用中的什么地方? IE。在 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;
}
}
我也在看这个 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 内存模型上;现在想想在弱内存模型上事情会变得多么困难!
当您放弃跨线程共享内存的标准模式和实践时,您就陷入了困境。别去那里