将一个循环分成两个循环的性能

时间:2012-03-09 13:23:04

标签: c++ loops big-o

美好的一天,

假设您有一个简单的for循环,如下所示......

for(int i=0;i<10;i++)
{
    //statement 1
    //statement 2
}

假设陈述1和陈述2为O(1)。除了&#34;开始&#34;另一个循环,会将for循环分解成两个(不是嵌套的,但顺序的)循环同样快吗?例如......

for(int i=0;i<10;i++)
{
    //statement 1
}
for(int i=0;i<10;i++)
{
    //statement 2
}

为什么我问这样一个愚蠢的问题是我有一个碰撞检测系统(CDS)必须遍历所有对象。我想&#34;区分&#34;我的CDS系统的功能,所以我可以简单地调用

cds.update(objectlist);

而不是打破我的CD系统。 (不要太担心我的CDS实施......我想我知道我在做什么,我只是不知道如何解释它,我真正需要知道的是,如果我采取一个巨大的循环遍历所有对象再次

6 个答案:

答案 0 :(得分:4)

就算法复杂性而言,分割循环没有区别。

就真实世界性能而言,分割循环可以提高性能,恶化性能或没有差别 - 这取决于操作系统,硬件,当然还有statement 1statement 2。< / p>

答案 1 :(得分:3)

这取决于您的申请。

可能的缺点(分裂):

  • 您的数据不适合L1数据缓存,因此您为第一个循环加载一次,然后为第二个循环重新加载它

可能的收益(分裂):

  • 你的循环包含许多变量,拆分有助于降低寄存器/堆栈压力,优化器将其转换为更好的机器代码
  • 您使用的函数会丢弃L1指令缓存,因此缓存会在每次迭代时加载,而通过拆分,您可以管理在每个循环的第一次迭代中加载一次(仅限)

这些列表肯定不全面,但您已经可以感觉到代码数据之间存在紧张关系。因此,当我们都不知道时,我们很难接受教育/猜测。

有疑问:个人资料。使用callgrind,检查每种情况下的缓存未命中,检查执行的指令数。衡量花费的时间。

答案 2 :(得分:2)

有两个循环你将支付:

  • 增加了生成的代码大小
  • 2x尽可能多的分支预测
  • 取决于语句1和2的数据布局,您可以将数据重新加载到缓存中。

最后一点可能会对这两个方向产生巨大影响。您应该像任何性能优化一样进行测量。

答案 3 :(得分:1)

就大的复杂性而言,如果1个循环是O(n),这就没有区别,那么2循环解决方案也是如此。
就微观优化而言,很难说。循环的成本相当小,我们不知道访问对象的成本是多少(如果它们在向量中,那么它也应该相当小),但要提供一个有用的东西还有很多需要考虑的因素。答案。

答案 4 :(得分:1)

如上所述,复杂性仍然存在。

但在现实世界中,我们无法预测哪个版本运行得更快。以下是扮演角色的因素,巨大的因素:

  • 数据缓存
  • 指令缓存
  • 推测执行
  • 分支预测
  • 分支目标缓冲区
  • CPU上可用的寄存器数
  • 缓存大小

(注意:在他们所有人身上,都有达摩克利斯的错误预测之剑;所有这些都是可以摧毁的并且可以转让)

特别是最后一个因素使得有时无法为性能依赖于特定高速缓存大小的代码编译一个真正的代码。有些应用程序在具有巨大缓存的CPU上运行速度更快,而在小缓存上运行速度较慢,而对于其他一些应用程序则相反。

解决方案:

  • 让您的编译器完成循环转换的工作。现代g ++在这个学科中相当不错。 g ++擅长的另一个学科是自动矢量化。请注意,编译器比几乎所有人都更了解计算机体系结构。
  • 发送不同的二进制文件和调度程序。
  • 使用适应目标缓存的cache-oblivious data structures/layouts and algorithms

尝试适应目标的软件总是一个好主意,理想情况下不会牺牲代码质量。在进行手动优化之前,无论是微观的还是宏观的,测量现实世界的运行,然后才进行优化。

文献: * Agner Fog's Guides * Intel's Guides

答案 5 :(得分:0)

您注意到通过创建第二个循环会产生一些性能开销是正确的。因此,它不能“同样快”;因为这个开销很小,仍然是开销。

我不会试图巧妙地谈论应该如何构建碰撞系统,但是如果你想要优化性能,最好避免构建不必要的控制结构,如果你可以在不拉扯头发的情况下进行管理。

请记住,过早优化是您可以做的最糟糕的事情之一。在我看来,当您遇到性能问题时,请考虑优化。