2015-11-13 77 views
3

我已經編寫了兩個程序,實現了一個簡單的矩陣乘法算法,一個用C++編寫,一個用Java編寫。與我的預期相反,Java程序的運行速度比C++程序快大約2.5倍。我是C++的新手,並且希望能夠在C++程序中對其進行更改以使其運行更快。性能優化:C++ vs Java沒有按預期執行

我的程序從此博客文章借用代碼和數據http://martin-thoma.com/matrix-multiplication-python-java-cpp

下面是我使用的是當前編譯標誌:

g++ -O3 main.cc  

javac Main.java 

以下是當前的編譯器/運行時版本:

$ g++ --version 
g++.exe (GCC) 4.8.1 
Copyright (C) 2013 Free Software Foundation, Inc. 
This is free software; see the source for copying conditions. There is NO 
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

$ java -version 
java version "1.8.0_05" 
Java(TM) SE Runtime Environment (build 1.8.0_05-b13) 
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode) 

我的電腦是〜2012年代的酷睿i3筆記本電腦上運行窗戶MinGW的。以下是當前的性能結果:

$ time ./a.exe < ../Testing/2000.in 
507584919 
real 0m36.469s 
user 0m0.031s 
sys  0m0.030s 

$ time java Main < ../Testing/2000.in 
507584919 
real 0m14.299s 
user 0m0.031s 
sys  0m0.015s 

這裏是C++程序:

#include <iostream> 
#include <cstdio> 
using namespace std; 

int *A; 
int *B; 
int height; 
int width; 

int * matMult(int A[], int B[]) { 
     int * C = new int[height*width]; 
     int n = height; 
     for (int i = 0; i < n; i++) { 
      for (int k = 0; k < n; k++) { 
       for (int j = 0; j < n; j++) { 
        C[width*i+j]+=A[width*i+k] * B[width*k+j]; 
       } 
      } 
     } 
     return C; 
} 

int main() { 
    std::ios::sync_with_stdio(false); 
    cin >> height; 
    cin >> width; 
    A = new int[width*height]; 
    B = new int[width*height]; 
    for (int i = 0; i < width*height; i++) { 
    cin >> A[i]; 
    } 

    for (int i = 0; i < width*height; i++) { 
    cin >> B[i]; 
    } 

    int *result = matMult(A,B); 
    cout << result[2]; 
} 

這裏是Java程序:

import java.util.*; 
import java.io.*; 

public class Main { 

    static int[] A; 
    static int[] B; 
    static int height; 
    static int width; 

public static void main(String[] args) { 
    try { 
     BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 
     height = Integer.parseInt(reader.readLine()); 
     width = Integer.parseInt(reader.readLine()); 
     A=new int[width*height]; 
     B=new int[width*height]; 
     int index = 0; 

     String thisLine; 
     while ((thisLine = reader.readLine()) != null) { 
      if (thisLine.trim().equals("")) { 
       break; 
      } else { 
       String[] lineArray = thisLine.split("\t"); 
       for (String number : lineArray) { 
        A[index] = Integer.parseInt(number); 
        index++; 
       } 
      } 
     } 

     index = 0; 
     while ((thisLine = reader.readLine()) != null) { 
      if (thisLine.trim().equals("")) { 
       break; 
      } else { 
       String[] lineArray = thisLine.split("\t"); 
       for (String number : lineArray) { 
        B[index] = Integer.parseInt(number); 
        index++; 
       } 
      } 
     } 

     int[] result = matMult(A,B); 
     System.out.println(result[2]); 

     reader.close(); 


    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
} 

public static int[] matMult(int[] A, int[] B) { 
     int[] C = new int[height*width]; 
     int n = height; 
     for (int i = 0; i < n; i++) { 
      for (int k = 0; k < n; k++) { 
       for (int j = 0; j < n; j++) { 
        C[width*i+j]+=A[width*i+k] * B[width*k+j]; 
       } 
      } 
     } 
     return C; 
    } 
} 

下面是一個2000×2000的測試用例的鏈接: https://mega.nz/#!sglWxZqb!HBts_UlZnR4X9gZR7bG-ej3xf2A5vUv0wTDUW-kqFMA

這裏是一個2x2測試用例的鏈接:https://mega.nz/#!QwkV2SII!AtfGuxPV5bQeZtt9eHNNn36rnV4sGq0_sJzitjiFE8s

任何意見都可以解釋我在C++中做錯了什麼,或者爲什麼我的C++實現比Java運行速度慢得多,我將非常感激!

編輯:建議,我修改了程序,以便他們不實際執行乘法,但只是讀取陣列並從每個打印出一個數字。以下是這方面的表現結果。 C++程序的IO速度較慢。這只是佔了部分差異。

$ time ./IOonly.exe < ../Testing/2000.in 
7 
944 
real 0m8.158s 
user 0m0.000s 
sys  0m0.046s 

$ time java IOOnly < ../Testing/2000.in 
7 
944 
real 0m1.461s 
user 0m0.000s 
sys  0m0.047s 
+1

您是否真的在兩種情況下測量了加載文件的時間? –

+0

只是做到了。將發佈上述結果。 – vancan1ty

+0

這兩個程序都看起來特別「快」。在Java代碼中,特別是使用正則表達式似乎可以保證減慢速度。總的來說,我認爲mircobenchmarks是一個壞主意。 – markspace

回答

4

我無法分析java執行,因爲它創建了一個臨時可執行模塊,它在「使用」後會消失。但是,我認爲它確實執行SSE指令來獲得該速度[或者它展開循環,如果您禁用SSE指令,那麼會發生這種情況]

但是用g ++(4.9.2)和clang ++編譯,我可以清楚地看到鏗鏘優化循環使用SSE指令,其中gcc沒有。由此產生的代碼正好慢了4倍。更改代碼以便在每個維度中使用2000的常量值[因此,編譯器「知道」高度和寬度的尺寸],gcc編譯器也生成需要大約8s(在我的機器上!)的代碼,而27s具有「可變」值[clang編譯的代碼在這裏也稍微快一些,但是在我說的噪音之內]。總體結論:編譯器的質量/聰明性將嚴重影響緊密循環的性能。代碼越複雜,代碼越多,C++解決方案就越有可能生成更好的代碼,在Java代碼中,簡單易用的編譯問題很可能會更好[通常,但不能保證]。我期望java編譯器使用分析來確定例如循環的數量。

編輯:

time結果可以被用來確定該文件的讀取花費很長的時間,但你需要某種形式的分析工具,以確定是否實際投入是使用很多CPU時間等。

Java引擎使用「即時編譯器」,該編譯器使用分析來確定特定代碼的命中次數(對於C++也可以這樣做,而大型項目通常也這樣做!) ,它允許它例如展開一個循環,或者在運行時確定循環中的迭代次數。假設這段代碼執行了2000 * 2000 * 2000循環,並且C++編譯器在知道值的大小時告訴我們Java運行時實際上並沒有做的更好(至少不是最初),只是做了一個更好的工作它設法隨着時間的推移改進性能。

不幸的是,由於java運行時的工作方式,它不會留下二進制代碼,所以我不能真正分析它的功能。

這裏的關鍵是你正在做的實際操作很簡單,邏輯很簡單,只是其中很多,而你正在使用一個微不足道的實現來完成它們。例如,Java和C++都將從手動展開循環中受益。

+0

墊子,謝謝你的回答。我提出了修改建議 - hardcode 2000的高度和寬度 - 在這種情況下,C++在我的計算機上也運行8秒,比原來快4倍。 看來這只是歸結爲編譯器實現。爲什麼java更容易在簡單的循環中執行,而C++在複雜的循環中會更好? – vancan1ty

+0

實際上,在這個特定的基準測試中,通過展開循環,clang甚至可以很好地擊敗gcc,即使你輪到SSE指令,結果比gcc版本快3倍(clang ++爲9.2s,g ++爲26.9s) 2000 values] –

+0

我以前的評論說,它在我的機器上下降到8秒是不正確的。我忘了重新加入我爲了測試IO時間而刪除的矩陣乘法。當我將數字設爲常數時,GCC的加速時間可以縮短到21秒。 – vancan1ty

2

C++在默認情況下比Java不是更快

C++是快如語言,但一旦你納入庫混進去,你一定對這些圖書館的速度。

該標準很難建立在性能,時期。標準庫是根據設計和正確性編寫的。

C++爲您提供了優化的機會!
如果您對標準庫的性能不滿意,您可以並且應該使用您自己的優化版本。例如,標準C++ IO對象在設計(流,區域設置,構面,內部緩衝區)方面很漂亮,但這會使它們在性能上變得糟糕。 如果您正在爲Windows操作系統編寫代碼,則可以使用ReadFileWriteConsole作爲您的IO機制。
如果您切換到這些功能而不是標準庫 - 您的程序的性能將比Java好幾個數量級。

+1

除了這個問題的NONE是關於速度一個圖書館(至少不在我的機器上)。執行代碼所需的時間在矩陣乘法中超過90%,大約7%是從文件中讀取整數(剩餘的3%在「各種其他小函數中很難確定它們是什麼for「,其中包括OS內核中處理內存管理的部分內容,但您可以期待在某些代碼中需要8秒運行並分配數兆字節的數據空間) –

+0

我不認爲,C++ std在性能上很糟糕。 沒有虛函數,所以編譯器優化發生了極端。 – Chameleon