【发布时间】:2011-04-27 07:48:56
【问题描述】:
我正在使用 Red Gate 的 Performance Profiler 优化物理模拟程序。处理碰撞检测的代码中有大约 52 项以下小检查,在两种情况下处理 3 维 26 个方向的单元格。
CollisionPrimitiveList cell = innerGrid[cellIndex + 1];
if (cell.Count > 0)
contactsMade += collideWithCell(obj, cell, data, ref attemptedContacts);
cell = innerGrid[cellIndex + grid.XExtent];
if (cell.Count > 0)
contactsMade += collideWithCell(obj, cell, data, ref attemptedContacts);
cell = innerGrid[cellIndex + grid.XzLayerSize];
if (cell.Count > 0)
contactsMade += collideWithCell(obj, cell, data, ref attemptedContacts);
作为程序的一个极其紧凑的循环,所有这些都必须使用相同的方法,但我突然发现,在我将区域从二维扩展到三维之后(从计数增加到 52 次检查) 16),突然 cell.Count 不再被内联,即使它是一个简单的吸气剂。
public int Count { get { return count; } }
这造成了巨大的性能损失,我花了相当长的时间才发现,当 cell.Count 在方法中出现 28 次或更少时,它每次都被内联,但是一旦 cell.Count 在方法中出现 29 次或更多,它一次都没有内联(尽管绝大多数调用来自代码中很少执行的最坏情况部分。)
回到我的问题,有人知道绕过这个限制吗?我认为简单的解决方案只是将计数字段设置为内部而不是私有,但我想要一个比这更好的解决方案,或者至少只是更好地了解情况。我希望在 Microsoft 的编写高性能托管应用程序页面 http://msdn.microsoft.com/en-us/library/ms973858.aspx 上会提到这类事情,但遗憾的是它不是(可能是因为 28 个计数限制是多么随意?)
我正在使用 .NET 4.0。
编辑:看起来我误解了我的小测试。我发现内联失败不是因为方法本身被调用了 28 次以上,而是因为按照某种标准,它们应该内联的方法“太长”。这仍然让我感到困惑,因为我看不出如何合理地不内联一个简单的 getter(并且内联它们的性能要好得多,因为我的分析器清楚地向我展示了),但显然 CLI JIT 编译器拒绝内联任何东西只是因为该方法已经很大(玩弄细微的变化告诉我这个限制是1500的代码大小(来自idasm),超过它没有进行内联,即使在我的getter的情况下,一些测试表明没有添加额外的代码要内联的开销)。
谢谢。
【问题讨论】:
-
如果你真的关心性能,你不会使用getter-setter。他们只是碍事。
-
有趣。听起来像是寄存器优化——也许反汇编 IL 会给出一些提示?
-
Ron,我想这可以满足我的需要,但我也喜欢通过不允许写访问来获得 getter 为我提供的保护(Count 实际上是链接中的节点数在这里列出,如果不小心改变了,事情可能会很糟糕)。 Jeremy,我可能会尝试查看 IL,但我的印象是它无济于事,因为内联仅由 CLR JIT 编译器完成。 (感谢我的分析器,我只能讲述内联)
-
阅读 blogs.msdn.com/b/davidnotario/archive/2004/11/01/250398.aspx 给了我这样的想法,即这种方法正在达到 O(n^2) 与 O(n) 的优化算法选择。来自该站点:“如果您找到解决方案的时间有限,一个好的方法可能是为小 Ns 做最佳解决方案,并为较大的 Ns 回退到非最优但快速的求解器。”我建议尝试将该方法拆分为 2 个子方法并连续调用它们 - 这可能会使您受到 JIT 的限制。
-
@Alk:将属性 getter 公开,并将下划线成员设为内部。那么你就不允许外部用户搞砸了。
标签: c# .net optimization .net-4.0