Java 何时以及如何正确使用循环优化和转换技术
首先,我想知道循环优化和转换之间的根本区别是什么 C中的一个简单循环如下所示:Java 何时以及如何正确使用循环优化和转换技术,java,c,optimization,loops,Java,C,Optimization,Loops,首先,我想知道循环优化和转换之间的根本区别是什么 C中的一个简单循环如下所示: for (i = 0; i < N; i++) { a[i] = b[i]*c[i]; } (i=0;i
for (i = 0; i < N; i++)
{
a[i] = b[i]*c[i];
}
(i=0;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];
}
(i=0;i
{
a[i*2]=b[i*2]*c[i*2];
a[i*2+1]=b[i*2+1]*c[i*2+1];
}
但是我们可以进一步展开它。但是我们可以展开它的极限是什么,我们如何找到它呢
还有很多技术,如循环耕作、循环分配等,如何确定何时使用合适的技术。这似乎与您的问题有点离题,但我不得不强调这一点的重要性 关键是要编写正确的代码,并使您的代码按照要求运行,而不必担心微观优化。
如果以后您发现您的程序缺乏性能,那么您可以配置文件您的应用程序可以找到问题区域,然后尝试对其进行优化。
请记住,正如一位智者所说,只有10%的代码运行了90%的应用程序运行时间。诀窍是通过分析识别代码,然后尝试对其进行优化。考虑到您的第一次优化尝试在50%的情况下都是错误的,我真的不会这么做尝试更复杂的方法(尝试任何奇数) 另外,不要将指数相乘,只需将2添加到i并再次循环到N即可避免不必要的移位(只要保持2的幂次,效果很小,但仍然如此)
总而言之:您创建了错误的、比编译器所能做的还要慢的代码-这就是我认为您不应该做这些事情的完美例子。我假设OP已经分析了他/她的代码,并且发现这段代码实际上很重要,并实际回答了以下问题:-): 编译器将根据对代码和处理器体系结构的了解,尝试做出循环展开决策 在加快速度方面
- 正如有人指出的,展开确实减少了循环终止条件和跳跃的数量李>
- 根据体系结构,硬件还可以支持在不添加额外指令的情况下对靠近内存位置的位置(例如,mov eax、[ebx+4])进行索引的有效方法(这可能会扩展到更多微操作-不确定)
- 大多数现代处理器使用无序执行来寻找指令级并行性。这是很难做到的,当下一个N指令在多个条件跳转之后(即,硬件需要能够丢弃可变级别的推测)。
- 有更多的机会提前对内存操作进行重新排序,从而隐藏数据获取延迟
- 代码矢量化(例如,转换为SSE/AVX)也可能发生,这在某些情况下允许并行执行代码。这也是展开的一种形式
- 展开会增加代码大小。编译器知道,超过指令代码缓存大小(所有现代处理器)、跟踪缓存(P4)、循环缓冲缓存(Core2/Nehalem/SandyBridge)、微操作缓存(SandyBridge)等都会受到惩罚。理想情况下,它使用静态成本效益试探法(特定代码和体系结构的函数)确定哪一级别的展开将产生最佳的整体净性能。根据编译器的不同,启发式可能会有所不同(我经常发现自己调整一下会很好)。
- 通常,如果循环包含大量代码,则展开的可能性较小,因为循环成本已摊销,有大量ILP可用,展开的代码膨胀成本过高。对于较小的代码段,循环可能会展开,因为成本可能较低。展开的实际数量将取决于体系结构、编译器试探法和代码的具体情况,并且将是编译器认为最佳的(可能不是:-))
- 当你认为编译器做的事情不对的时候。编译器可能不够复杂(或不够最新),无法以最佳方式使用您正在处理的体系结构知识
- 很可能,试探法失败了(毕竟它们只是试探法)。通常,如果您知道这段代码非常重要,请尝试展开它,如果它提高了性能,请保留它,否则将其丢弃。另外,只有当您大致拥有整个系统时,才可以这样做,因为当您的代码工作集为20k时,可能有益的是,当您的代码工作集为31k时,可能不会有益