为什么编译器没有优化掉这段代码?

4
我有一段代码,使用第三方工具迭代一组点的集合。
for (int i = 0; i < pcoll.PointCount; i++) { /* ... */ }

当使用dotTrace进行分析时,我注意到每次迭代都会访问PointCount属性(如上图所示)。
我期望这个属性的值会被编译器优化掉,但显然并没有发生。也许这实际上是基于COM的第三方库或在收集信息时由dotTrace自身引起的问题。
我不确定这个话题是否更适合于Gis.StackExchange。然而,也许有人知道在什么情况下不会发生优化,或者它可能会发生的方式。

1
我不确定,但我认为这与C#中的属性或方法有关。因此,它可能无法静态地知道pcoll.PointCount的值。根据堆栈跟踪,调用了get方法。除非可以内联,否则无法对其进行优化。 - t3dodson
现在已经缓存,该属性仅被访问一次。int pointCount = pcoll.PointCount; for (int i = 0; i < pointCount ; i++) { /*.. */ } - Tim Schmelter
@TomDDD 内联是小问题 - 更大的问题是编译器无法知道是否安全存储 PointCount 的值,因为只有在 pcoll 是不可变的且 PointCount 是纯的情况下才可能实现,而编译器无法知道这一点。通常,编译器会忽略第一个点(也就是说,它们会忽略跨线程访问 - 在同一线程中访问可能也会防止“优化”),但第二个点是确定的 - 编译器根本不允许进行这种类型的优化。 - Luaan
3个回答

7
简单来说,编译器如何知道pcoll.PointCount在调用之间是否会改变?它不能安全地假设该值将保持不变,因此无法通过缓存对pcoll.PointCount的第一次调用的值来优化此代码。

有时候很简单,我通过实现自己的属性来检查这个问题。显然,调试器在每次迭代中都会逐步执行它,谢谢。 - MakePeaceGreatAgain

2

这段时间内它可能已经发生了改变。

事实上,测试i < pcoll.PointCount每一次迭代而不是只使用foreach(var point in pcoll)的原因之一,就在于认为集合可能会被改变,而枚举器不能保证处理它们枚举的集合的更改。

这与通过本地变量访问的数组有所不同,因为通过本地变量访问的数组的Length能够发生变化的唯一方式是当局部作出了更改时。

即使在此情况下,也值得记住编译器常常会跳过一些明显的优化,因为它知道JIT编译器也会做出相同的优化。


事实上,不使用foreach的原因是ESRI提供的PointCollection类没有实现枚举器。这当然很恼人,但就是这样。 - MakePeaceGreatAgain
真的吗?http://help.arcgis.com/en/webapi/silverlight/apiref/ESRI.ArcGIS.Client~ESRI.ArcGIS.Client.Geometry.PointCollection.html 上说它是从ObservableCollection<MapPoint>继承而来的。GetEnumerator()会抛出异常吗?如果您想防止对集合进行类似的更改,那么使用for的原因之一就是这个。 - Jon Hanna
抱歉,我指的是 IPointCollection 接口(请参见此处:http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#/IPointCollection_Interface/002m00000275000000/),在这里我们根本没有任何 PointCollection 类,只有一些多部分几何体(例如 MultiPoint)。但实际上我指的是第一个。 - MakePeaceGreatAgain
嗯,那会很烦人。 - Jon Hanna
我不知道ESRI的人为什么决定根本不实现IEnumerator(对于其他类也是如此),也许可变性可能是一个原因。 - MakePeaceGreatAgain

1
预期的优化对于字段是正确的。但是属性具有setter/getter(访问属性实际上是将它们作为方法调用),因此编译器将很难尝试优化它。
要修复,请将其转换为字段或只读取一次。
var max = pcoll.PointCount;
for (int i = 0; i < max; i++) { /* ... */ }

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