【问题标题】:Performance optimization: C++ vs Java not performing as expected性能优化:C++ 与 Java 未按预期执行
【发布时间】:2016-02-15 15:56:49
【问题描述】:

我编写了两个程序来实现一个简单的矩阵乘法算法,一个用 C++ 编写,一个用 Java 编写。与我的预期相反,Java 程序的运行速度比 C++ 程序快 2.5 倍。我是 C++ 的新手,想了解我可以在 C++ 程序中进行哪些更改以使其运行得更快的建议。

我的程序借用了这篇博文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 年代 core i3 笔记本电脑,运行 Windows 和 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;
    }
}

这是一个 2000x2000 测试用例的链接: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

【问题讨论】:

  • 你有没有实际测量过这两种情况下只加载文件的时间?
  • 就是这样做的。将在上面发布结果。
  • 这两个程序看起来都不是特别“快”。在 Java 代码中,特别是使用正则表达式似乎可以保证减慢速度。总的来说,我认为 mircobenchmarks 是个坏主意。
  • @MatsPetersson 我同意。与 Java 中的缓冲行读取相比,C++ 中的非缓冲值读取速度很可能很慢。
  • 这似乎是一个愚蠢的问题,但是您总是先检查 C++ 程序,然后检查 Java 程序吗?这可能会在 Java 程序运行时将输入文件留在缓存中。

标签: java c++ performance optimization


【解决方案1】:

我无法分析 java 执行,因为它创建了一个临时可执行模块,该模块在“使用”后消失。但是,我假设它确实执行 SSE 指令以获得该速度[或者它展开循环,如果您禁用 SSE 指令,clang++ 会这样做]

但是使用 g++ (4.9.2) 和 clang++ 编译,我可以清楚地看到 clang 优化了循环以使用 SSE 指令,而 gcc 没有。因此,生成的代码正好慢了 4 倍。更改代码以使其在每个维度中使用 2000 的常量值 [因此编译器“知道”高度和宽度的维度],gcc 编译器还生成大约需要 8 秒的代码(在我的机器上!),而 27 秒具有“变量”值[clang 编译的代码在这里也稍微快一些,但在我想说的噪音范围内]。

总体结论:编译器的质量/聪明程度会极大地影响紧密循环的性能。代码越复杂和多样,C++ 解决方案就越有可能生成更好的代码,而简单且易于编译的问题很可能在 Java 代码中更好[通常,但不能保证]。例如,我希望 java 编译器使用分析来确定循环数。

编辑:

time 的结果可用于确定文件的读取是否需要很长时间,但您需要某种分析工具来确定实际输入是否使用大量 CPU 时间等.

Java 引擎使用“即时编译器”,它使用分析来确定特定代码段被命中的次数(您也可以在 C++ 中这样做,大型项目经常这样做!),例如,这允许它展开循环,或在运行时确定循环中的迭代次数。鉴于此代码执行了 2000 * 2000 * 2000 循环,并且 C++ 编译器在知道值的大小时实际上做得更好,这告诉我们 Java 运行时实际上并没有做得更好(至少最初不是),只是随着时间的推移,它设法提高了性能。

不幸的是,由于 java 运行时的工作方式,它不会留下二进制代码,所以我无法真正分析它的作用。

这里的关键是你所做的实际操作很简单,逻辑也很简单,只是数量很多,而且你正在使用微不足道的实现来完成它们。例如,Java 和 C++ 都将受益于手动展开循环。

【讨论】:

  • 垫子,谢谢你的回答。我做了你建议的修改——对高度和宽度进行硬编码 2000——在这种情况下,C++ 在我的计算机上也可以在 8 秒内运行,比原来的速度快 4 倍以上。看起来这只是归结为编译器实现。为什么 java 在简单循环上表现更好,而 C++ 在复杂循环上表现更好?
  • 实际上,clang 通过展开循环很好地在这个特定的基准上击败了 gcc,即使你打开 SSE 指令,最终还是比 gcc 版本快了大约 3 倍(clang++ 为 9.2s,clang++ 为 26.9s g++ 编译代码)[没有常量 2000 值]
  • 我之前的评论说它在我的机器上下降到 8 秒是不正确的。我忘记加回矩阵乘法,我删除了它来测试 IO 时间。当我将数字设为常量时,我​​确实可以使用 GCC 获得很好的加速,降低到 21 秒。
  • 嗯。在我的机器上,“实时”时间与“用户”和“系统”时间之和的 1 毫秒相匹配,这意味着没有时间损失。您的主目录是在网络驱动器上还是类似的?
  • 我不认为“真实”、“用户”和“系统”时间可以用来衡量将矩阵读入程序并将它们存储在内存中的时间。如果把矩阵乘法注释掉,C++程序在我的电脑上大约需要8秒。
【解决方案2】:

默认情况下,C++ 并不比 Java 快

C++ 作为一门语言速度很快,但是一旦您将库纳入其中,您就会受到这些库的速度的约束。

该标准几乎不是为性能而建立的。标准库的编写考虑了设计和正确性。

C++ 为您提供优化的机会!
如果您对标准库的性能不满意,您可以并且应该使用您自己的优化版本。

例如,标准 C++ IO 对象在设计(流、语言环境、构面、内部缓冲区)方面很漂亮,但这使得它们的性能很糟糕。 如果您正在为 Windows 操作系统编写代码,则可以使用 ReadFileWriteConsole 作为 IO 机制。
如果您切换到这些函数而不是标准库 - 您的程序的性能要比 Java 高几个数量级。

【讨论】:

  • 除了这个问题没有一个是关于图书馆的速度(至少不是在我的机器上)。执行代码的时间90%以上是在矩阵乘法中,大约7%是从文件中读取整数(剩下的3%在“其他各种难以确定的小函数是什么” for",包括操作系统内核中处理内存管理的某些部分,但您可以期望在某些代码中需要 8 秒才能运行并分配数兆字节的数据空间)
  • 我不认为,c++ std 在性能上很糟糕。没有虚函数,所以会进行极端的编译器优化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-20
  • 2017-12-15
  • 2011-02-25
  • 2012-11-23
  • 2014-10-20
  • 2015-09-17
相关资源
最近更新 更多