【问题标题】:Why is C array so much faster than std::array? [duplicate]为什么 C 数组比 std::array 快这么多? [复制]
【发布时间】:2017-08-28 05:22:49
【问题描述】:

我们目前正在用 C++ 编写一些性能关键代码,这些代码可在许多大型矩阵和向量上运行。关于我们的研究,std::array 和标准 C 数组之间应该没有太大的性能差异 (见This questionthis)。 然而,在测试过程中,通过使用 C 数组而不是 std::array,我们体验到了巨大的性能提升。 这是我们的演示代码:

#include <iostream>
#include <array>
#include <sys/time.h>

#define ROWS 784
#define COLS 100
#define RUNS 50

using std::array;

void DotPComplex(array<double, ROWS> &result, array<double, ROWS> &vec1, array<double, ROWS> &vec2){
  for(int i = 0; i < ROWS; i++){
    result[i] = vec1[i] * vec2[i];
  }
}

void DotPSimple(double result[ROWS], double vec1[ROWS], double vec2[ROWS]){
  for(int i = 0; i < ROWS; i++){
    result[i] = vec1[i] * vec2[i];
  }
}

void MatMultComplex(array<double, ROWS> &result, array<array<double, COLS>, ROWS> &mat, array<double, ROWS> &vec){
  for (int i = 0; i < COLS; ++i) {
      for (int j = 0; j < ROWS; ++j) {
        result[i] += mat[i][j] * vec[j];
      }
  }
}

void MatMultSimple(double result[ROWS], double mat[ROWS][COLS], double vec[ROWS]){
  for (int i = 0; i < COLS; ++i) {
      for (int j = 0; j < ROWS; ++j) {
        result[i] += mat[i][j] * vec[j];
      }
  }
}

double getTime(){
    struct timeval currentTime;
    gettimeofday(&currentTime, NULL);
    double tmp = (double)currentTime.tv_sec * 1000.0 + (double)currentTime.tv_usec/1000.0;
    return tmp;
}

array<double, ROWS> inputVectorComplex = {{ 0 }};
array<double, ROWS> resultVectorComplex = {{ 0 }};
double inputVectorSimple[ROWS] = { 0 };
double resultVectorSimple[ROWS] = { 0 };

array<array<double, COLS>, ROWS> inputMatrixComplex = {{0}};
double inputMatrixSimple[ROWS][COLS] = { 0 };

int main(){
  double start;
  std::cout << "DotP test with C array: " << std::endl;
  start = getTime();
  for(int i = 0; i < RUNS; i++){
    DotPSimple(resultVectorSimple, inputVectorSimple, inputVectorSimple);
  }
  std::cout << "Duration: " << getTime() - start << std::endl;

  std::cout << "DotP test with C++ array: " << std::endl;
  start = getTime();
  for(int i = 0; i < RUNS; i++){
    DotPComplex(resultVectorComplex, inputVectorComplex, inputVectorComplex);
  }
  std::cout << "Duration: " << getTime() - start << std::endl;

  std::cout << "MatMult test with C array : " << std::endl;
  start = getTime();
  for(int i = 0; i < RUNS; i++){
    MatMultSimple(resultVectorSimple, inputMatrixSimple, inputVectorSimple);
  }
  std::cout << "Duration: " << getTime() - start << std::endl;

  std::cout << "MatMult test with C++ array: " << std::endl;
  start = getTime();
  for(int i = 0; i < RUNS; i++){
    MatMultComplex(resultVectorComplex, inputMatrixComplex, inputVectorComplex);
  }
  std::cout << "Duration: " << getTime() - start << std::endl;
}

编译:icpc demo.cpp -std=c++11 -O0 结果如下:

DotP test with C array: 
Duration: 0.289795 ms
DotP test with C++ array: 
Duration: 1.98413 ms
MatMult test with C array : 
Duration: 28.3459 ms
MatMult test with C++ array: 
Duration: 175.15 ms

带有-O3 标志:

DotP test with C array: 
Duration: 0.0280762 ms
DotP test with C++ array: 
Duration: 0.0288086 ms
MatMult test with C array : 
Duration: 1.78296 ms
MatMult test with C++ array: 
Duration: 4.90991 ms

在没有编译器优化的情况下,C 数组实现要快得多。为什么? 使用编译器优化,点积同样快。但是对于矩阵乘法,使用 C 数组时仍然有显着的加速。 有没有办法在使用std::array时达到同等性能?


更新:

使用的编译器:icpc 17.0.0

使用gcc 4.8.5,我们的代码运行速度比使用任何优化级别的英特尔编译器慢得多。因此,我们主要对 intel 编译器的行为感兴趣。

根据Jonas 的建议,我们调整了RUNS 50.000,结果如下(英特尔编译器):

带有-O0 标志:

DotP test with C array: 
Duration: 201.764 ms
DotP test with C++ array: 
Duration: 1020.67 ms
MatMult test with C array : 
Duration: 15069.2 ms
MatMult test with C++ array: 
Duration: 123826 ms

带有-O3 标志:

DotP test with C array: 
Duration: 16.583 ms
DotP test with C++ array: 
Duration: 15.635 ms
MatMult test with C array : 
Duration: 980.582 ms
MatMult test with C++ array: 
Duration: 2344.46 ms

【问题讨论】:

  • 另外,你的综合基准测试并不是很有用。您主要是在测量编译器是否可以确定结果是未使用的(在这种情况下,所有计算都可以省略)还是恒定的(在这种情况下,编译器可以在编译时完成所有计算)。在这两种情况下,您都没有衡量任何有用的东西。
  • 因为您已经禁用优化而没有被内联的函数调用而支付了抽象损失?
  • 如果您希望编译器优化抽象,请启用优化。
  • godbolt.org/g/9MnTLs 上,MatMultComplexMatMultSimple 似乎都产生了相同的程序集
  • 这不是 C 题,所以我去掉了 C 标签。

标签: c++ arrays performance c++11 stdarray


【解决方案1】:

首先,您使用的运行次数太少了。就个人而言,我没有意识到(在运行代码之前)您的“持续时间”测量值以 毫秒

为单位

通过将DotPSimpleDotPComplexRUNS 增加到5,000,000,时间类似于:

使用 C 数组进行 DotP 测试:

持续时间:1074.89

使用 C++ 数组进行 DotP 测试:

持续时间:1085.34

也就是说,它们非常接近于同样快。事实上,由于基准测试的随机性,哪个测试速度最快会因测试而异。 MatMultSimpleMatMultComplex 也是如此,尽管它们只需要运行 50,000 次。

如果您真的想测量并了解更多信息,您应该接受此基准的随机性,并近似“持续时间”测量的分布。包括函数的随机顺序,以消除任何排序偏差。

编辑: assembly code(来自 user2079303 的回答)完全证明启用优化没有区别。因此,零成本抽象实际上是启用优化的零成本,这是一个合理的要求。

更新:

我使用的编译器:

g++ (Debian 6.3.0-6) 6.3.0 20170205

使用以下命令:

g++ -Wall -Wextra -pedantic -O3 test.cpp

使用此处理器:

Intel(R) Core(TM) i5-4300U CPU @ 1.90GHz

【讨论】:

  • 您使用哪个编译器以及哪个优化级别。就我而言,增加 RUNS 根本不会改变比率。
  • @TheFloe 我已经更新了我的答案
【解决方案2】:

为什么...在没有编译器优化的情况下要快得多。为什么?

无论出于何种原因编译器都会选择。如果您不让编译器进行优化,那么您就不能期望两个不同的代码具有相似的性能,即使它们具有相同的行为。启用优化后,编译器或许可以将抽象代码转化为高效代码,性能应该相当。

std::array 的使用涉及函数调用,而指针的使用则不涉及。例如,std::array::operator[] 是一个函数,而指针的下标运算符则不是。进行函数调用可能比不进行函数调用要慢。所有这些函数调用都可以被优化掉(扩展内联),但如果您选择不启用优化,那么函数调用仍然存在。

但是对于矩阵乘法,使用 C 数组时仍然有显着的加速。

可能是您的基准测试或编译器中的一个怪癖。 Here 两个函数具有相同的程序集,因此具有相同的性能

编辑:我同意乔纳斯的回答。基准测试的迭代次数太少。此外,如果不重复基准测试并分析偏差,就不可能说两次测量之间的差异是否显着。


结论是:

  • 启用优化后,C 数组快于std::array。如链接所示,至少在使用 clang 3.9.1 编译时不会。也许您的编译器会生成不同的程序集,但我认为没有理由这样做。

  • C++ 的零成本抽象只有经过优化才能实现零成本。

  • 编写有意义的微基准测试并非易事。

【讨论】:

  • 矩阵的 C 版本与 std::array 版本不同。 C 数组只是 ROWS * COLS 双精度块。 std::array 版本至少在概念上是一个数组数组。这意味着要完全取消引用它需要两个下标操作,而 C 版本是一个计算。令我印象深刻的是,一些现代 C++ 编译器能够几乎完全优化抽象开销。
  • @JeremyP 当然,二维数组在概念上也是一个数组数组。我看到的唯一区别是std::array 的两个取消引用在单独的(内联)函数调用中,因此它可能对优化传递的顺序很敏感。必须先扩展函数,然后才能合并取消引用,而指针的两个取消引用甚至在内联之前就已经存在。坦率地说,如果汇编输出不(至少几乎)相同,我会很失望。
  • @user2079303 我部分同意你的回答。增加 RUNS 不会改变我们编译器的行为。比例保持不变。在遵循 Uli Schlachters 评论后,我发现有一个“基准测试中的怪癖”:如果在计算后实际使用函数的结果,则两种实现的性能大致相同。
  • @TheFloe 我建议比较编译器给出的程序集输出,看看它们有何不同。
猜你喜欢
  • 2014-04-11
  • 2015-03-09
  • 1970-01-01
  • 2021-12-27
  • 2018-11-02
  • 1970-01-01
  • 2014-02-05
  • 2014-05-28
  • 1970-01-01
相关资源
最近更新 更多