![](/img/trans.png)
[英]Why is this Clojure code so slow compared to the alternative in Java?
[英]Why is this C++ code execution so slow compared to java?
我最近用Java编写了一个计算密集型算法,然后将其翻译成C ++。 令我惊讶的是,C ++的执行速度要慢得多。 我现在编写了一个更短的Java测试程序,以及相应的C ++程序 - 见下文。 我的原始代码具有很多数组访问权限,测试代码也是如此。 C ++的执行时间要长5.5倍(请参阅每个程序末尾的注释)。
结论在下面的第 1 21条评论之后......
测试代码:
g++ -o ...
Java速度提高了5.5倍 g++ -O3 -o ...
Java快2.9倍 g++ -fprofile-generate -march=native -O3 -o ...
(运行,然后g++ -fprofile-use
等)Java快1.07倍。 我原来的项目(比测试代码复杂得多):
Software environment:
Ubuntu 16.04 (64 bit).
Netbeans 8.2 / jdk 8u121 (java code executed inside netbeans)
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
Compilation: g++ -o cpp_test cpp_test.cpp
Java代码:
public class JavaTest {
public static void main(String[] args) {
final int ARRAY_LENGTH = 100;
final int FINISH_TRIGGER = 100000000;
int[] intArray = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) intArray[i] = 1;
int i = 0;
boolean finished = false;
long loopCount = 0;
System.out.println("Start");
long startTime = System.nanoTime();
while (!finished) {
loopCount++;
intArray[i]++;
if (intArray[i] >= FINISH_TRIGGER) finished = true;
else if (i <(ARRAY_LENGTH - 1)) i++;
else i = 0;
}
System.out.println("Finish: " + loopCount + " loops; " +
((System.nanoTime() - startTime)/1e9) + " secs");
// 5 executions in range 5.98 - 6.17 secs (each 9999999801 loops)
}
}
C ++代码:
//cpp_test.cpp:
#include <iostream>
#include <sys/time.h>
int main() {
const int ARRAY_LENGTH = 100;
const int FINISH_TRIGGER = 100000000;
int *intArray = new int[ARRAY_LENGTH];
for (int i = 0; i < ARRAY_LENGTH; i++) intArray[i] = 1;
int i = 0;
bool finished = false;
long long loopCount = 0;
std::cout << "Start\n";
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long long startTime = (1000000000*ts.tv_sec) + ts.tv_nsec;
while (!finished) {
loopCount++;
intArray[i]++;
if (intArray[i] >= FINISH_TRIGGER) finished = true;
else if (i < (ARRAY_LENGTH - 1)) i++;
else i = 0;
}
clock_gettime(CLOCK_REALTIME, &ts);
double elapsedTime =
((1000000000*ts.tv_sec) + ts.tv_nsec - startTime)/1e9;
std::cout << "Finish: " << loopCount << " loops; ";
std::cout << elapsedTime << " secs\n";
// 5 executions in range 33.07 - 33.45 secs (each 9999999801 loops)
}
在使用分析信息时,我唯一能让C ++程序超越Java的时间。 这表明运行时信息中有一些东西(默认情况下是Java),可以更快地执行。
除了一个非平凡的if语句之外,你的程序中没有太多事情发生。 也就是说,如果不分析整个程序,很难预测哪个分支最有可能。 这让我相信这是一个分支错误预测问题。 现代CPU执行指令流水线操作 ,从而实现更高的CPU吞吐量。 但是,这需要预测下一条要执行的指令。 如果猜测错误,则必须清除指令管道,并加载正确的指令(这需要时间)。
在编译时,编译器没有足够的信息来预测哪个分支最有可能。 CPU也做了一些分支预测,但这通常是循环循环和ifs if(而不是其他)。
但是,Java具有能够在运行时和编译时使用信息的优点。 这允许Java将中间分支识别为最频繁出现的分支,因此为管道预测了此分支。
不知怎的,GCC和clang都无法展开这个循环,即使在-O3和-Os中也不会出现不变量,但Java确实如此。
Java的最终JITted汇编代码与此类似(实际上重复了两次 ):
while (true) {
loopCount++;
if (++intArray[i++] >= FINISH_TRIGGER) break;
loopCount++;
if (++intArray[i++] >= FINISH_TRIGGER) break;
loopCount++;
if (++intArray[i++] >= FINISH_TRIGGER) break;
loopCount++;
if (++intArray[i++] >= FINISH_TRIGGER) { if (i >= ARRAY_LENGTH) i = 0; break; }
if (i >= ARRAY_LENGTH) i = 0;
}
通过这个循环,我得到了C ++和Java之间完全相同的时序(6.4s)。
为什么合法呢? 因为ARRAY_LENGTH
是100,这是4的倍数。所以i
只能超过100并且每4次迭代重置为0。
这看起来像GCC和clang的改进机会; 它们无法展开迭代总数未知的循环,但即使强制展开,它们也无法识别仅适用于某些迭代的循环部分。
关于你在一个更复杂的代码(也就是现实生活)中的发现:Java的优化器对于小循环非常有用,已经考虑了很多,但Java在虚拟调用和GC上浪费了大量时间。
最后,它归结为在具体架构上运行的机器指令,无论谁想出最好的集合,都会获胜。 不要以为编译器会“做正确的事”,看看和生成的代码,配置文件,重复。
例如,如果你稍微重构一下你的循环:
while (!finished) {
for (i=0; i<ARRAY_LENGTH; ++i) {
loopCount++;
if (++intArray[i] >= FINISH_TRIGGER) {
finished=true;
break;
}
}
}
然后C ++将胜过Java(5.9s vs 6.4s)。 ( 修订后的C ++程序集 )
如果你可以允许轻微的溢出(在达到退出条件后增加更多的intArray
元素):
while (!finished) {
for (int i=0; i<ARRAY_LENGTH; ++i) {
++intArray[i];
}
loopCount+=ARRAY_LENGTH;
for (int i=0; i<ARRAY_LENGTH; ++i) {
if (intArray[i] >= FINISH_TRIGGER) {
loopCount-=ARRAY_LENGTH-i-1;
finished=true;
break;
}
}
}
现在clang能够对循环进行矢量化并达到3.5s的速度,而Java的4.8s (遗憾的是GCC仍然无法对其进行矢量化)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.