![](/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.