2015-11-13 46 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