编译器指令重新排序的隐含限制?

3

最近在学习锁机制和并发编程时,我了解到编译器指令重排。自此以后,即使字段没有并发访问,我对自己编写的代码的正确性也更加怀疑。我刚刚遇到了这样一段代码:

var now = DateTime.Now;
var newValue = CalculateCachedValue(); 
cachedValue = newValue;
lastUpdate = now;

cachedValue被赋予新值之前,lastUpdate = now是否有可能被执行?如果是这样的话,那么如果运行此代码的线程被取消,我将记录一个未保存的更新。根据我目前所了解的情况,我必须假设这种情况是存在的并且需要使用内存屏障。

但是第一条语句在第二条语句之后执行是否真的可能?这意味着now是计算之后的时间而不是之前。我猜测这不可能发生,因为涉及到方法调用。但是,并没有其他明确的依赖关系来防止重新排序。方法调用/属性访问是否是隐式屏障?还有其他指令重排的隐含约束条件吗?


在单线程情况下,您无法检测到优化。不用担心。如果使用Thread.Abort“中止”,则所有赌注都将失效,并且单个中止调用可能会永久损坏AppDomain。 - usr
1个回答

5
.NET的Jitter可以重新排列指令。不变代码移动和公共子表达式消除是重要的优化,可以使代码运行更快。
但这并不是随意发生的。只有在优化器知道重新排序不会产生任何不良副作用时,它才会考虑这样的优化。为了让优化器知道,它首先必须内联方法或属性getter调用。这永远不会发生在`DateTime.Now`上,因为它需要一个操作系统调用,而这些调用永远无法内联。因此,您有一个硬保证,即任何语句都不会在`var now = DateTime.Now;`之前或之后移动。
移动代码实际上必须是有意义的,并且可以带来可衡量的好处。重新排序赋值语句没有任何意义,不会使代码运行更快。不变代码移动是一种应用于循环内语句的优化,将这样的语句移到循环之前,以便它不会被重复执行,这将得到回报。在该片段中完全不存在此风险。在这里也看不到公共子表达式消除。
担心由优化器引起的错误有点像害怕出门,因为你可能会被闪电击中。那确实会发生。但概率非常非常低。使用.NET Jitter的一个好处是它每天都经过数百万次测试的保证。

非常感谢!我完全理解了系统调用和内联的部分。然而,我对于你最后关于优化器引起的错误的陈述并不完全满意。我认为我的问题不够精确。我会发布另一个更好地说明我担心的潜在错误的问题。 - lex82

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接