何时以及如何正确使用循环优化和转换技术

时间:2011-12-18 14:07:45

标签: java c optimization loops

首先,我想知道循环优化和转换之间的根本区别,还

C中的一个简单循环如下:

for (i = 0; i < N; i++)
{
a[i] = b[i]*c[i];
}

但我们可以展开它:

for (i = 0; i < N/2; i++) 
{
a[i*2] = b[i*2]*c[i*2];
a[i*2 + 1] = b[i*2 + 1]*c[i*2 + 1];
}

但我们可以进一步展开它..但是我们可以展开它的限制是什么,我们如何找到它。

还有更多技术,如循环耕作,循环分配等。 ,如何确定何时使用合适的。

3 个答案:

答案 0 :(得分:4)

我会假设OP已经描述了他/她的代码并且发现这段代码实际上很重要,并且实际上回答了问题:-):

编译器将尝试根据它对代码和处理器体系结构的了解,使循环展开决策。

在提高速度方面。

  • 正如有人指出的那样,展开会减少循环终止条件比较和跳转的次数。
  • 根据体系结构的不同,硬件也可能支持一种有效的方法来索引近存储器位置(例如,mov eax,[ebx + 4]),而无需添加额外的指令(这可能会扩展到更多的微操作 - 不确定)。
  • 大多数现代处理器使用乱序执行,以查找指令级并行。当下一个N指令在多次条件跳转之后(即,硬件需要能够丢弃可变的推测级别)时,这很难做到。
    • 之前有更多机会对内存操作进行重新排序,以便隐藏数据提取延迟。
    • 也可以发生代码矢量化(例如,转换为SSE ​​/ AVX),其允许在某些情况下并行执行代码。这也是展开的一种形式。

在决定何时停止展开方面:

  • 展开会增加代码大小。编译器知道超出指令代码高速缓存大小(所有现代处理器),跟踪高速缓存(P4),循环缓冲区高速缓存(Core2 / Nehalem / SandyBridge),微操作高速缓存(SandyBridge)等会受到惩罚。理想情况下它使用静态成本效益启发式(特定代码和架构的一个功能),以确定哪个展开级别将产生最佳的整体净性能。根据编译器的不同,heurstics可能会有所不同(通常我发现调整它自己会很好)。
    • 通常,如果循环包含大量代码,则不太可能展开,因为循环成本已经摊销,有足够的ILP可用,并且展开的代码膨胀成本过高。对于较小的代码片段,循环可能会展开,因为成本可能很低。实际的展开次数将取决于架构的细节,编译器启发式和代码,并且将是编译器决定的最佳(可能不是:-))。

就你何时应该进行这些优化而言:

  • 当你不认为编译器做了正确的事情。编译器可能不够复杂(或足够最新),足以使用您正在最佳工作的架构知识。

  • 可能,启发式失败了(毕竟它们只是启发式)。一般来说,如果您知道这段代码非常重要,请尝试展开它,如果它提高了性能,请保留它,否则将其丢弃。此外,只有当您拥有大致整个系统时才这样做,因为当您的代码工作集为20k时,当您的代码工作集为31k时可能没有益处。

答案 1 :(得分:3)

对于你的问题,这似乎不是主题,但我不得不强调这一点的重要性。

关键是要编写正确的代码,让代码按照要求运行,而不必担心微观优化。
如果以后您发现您的程序缺乏性能,那么您配置文件!! 您的应用程序以找到问题区域然后尝试优化它们。
请记住,其中一位有智慧的人说It is only 10% of your code which runs 90% of the total run time of your application trick is to identify that code through profiling and then try to optimize it.

答案 2 :(得分:2)

考虑到你的第一次优化尝试已经错误在50%的情况下我真的不会尝试更复杂的东西(尝试任何奇数)。

而不是将你的指数相乘,只需将i加2并再次循环到N--避免不必要的移位(只要我们保持2的幂,但仍然是次要效果)

总结一下:你创建了不正确,慢的代码而不是编译器可以做的 - 这就是为什么你不应该做我认为的这些东西的完美例子。