为什么C数组比std :: array快得多?

The*_*loe 16 c++ arrays performance c++11 stdarray

我们目前正在用C++编写一些性能关键代码,它们可以在许多大型矩阵和向量上运行.关于我们的研究,std::array标准C阵列之间应该没有重大的性能差异(参见这个问题或者这个).但是,在测试过程中,我们通过使用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;
}
Run Code Online (Sandbox Code Playgroud)

编译: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
Run Code Online (Sandbox Code Playgroud)

-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
Run Code Online (Sandbox Code Playgroud)

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


更新:

编译器使用: icpc 17.0.0

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

正如Jonas所建议的,我们调整RUNS 50.000了以下结果(intel编译器):

-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
Run Code Online (Sandbox Code Playgroud)

-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
Run Code Online (Sandbox Code Playgroud)

Jon*_*nas 20

首先,你使用的运行量太少了.就个人而言,我没有意识到(在运行代码之前)你的"持续时间"测量是以毫秒为单位

通过增加RUNS5,000,000的DotPSimpleDotPComplex时机是这样的:

使用C数组进行DotP测试:

持续时间:1074.89

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

持续时间:1085.34

也就是说,他们非常接近同样快.事实上,由于基准的随机特性,从测试到测试的速度最快.同样是真正的MatMultSimpleMatMultComplex,虽然他们只需要50000点运行.

如果你真的想测量和了解更多,你应该接受这个基准的随机性质并近似"持续时间"测量的分布.包括函数的随机顺序,以消除任何排序偏差.

编辑:汇编代码(来自user2079303的答案)完全证明启用优化没有差异.因此,零成本抽象实际上是零成本且启用了优化,这是合理的要求.

更新:

我用的编译器:

g++ (Debian 6.3.0-6) 6.3.0 20170205
Run Code Online (Sandbox Code Playgroud)

使用以下命令:

g++ -Wall -Wextra -pedantic -O3 test.cpp
Run Code Online (Sandbox Code Playgroud)

使用此处理器:

Intel(R) Core(TM) i5-4300U CPU @ 1.90GHz
Run Code Online (Sandbox Code Playgroud)


eer*_*ika 10

为什么......没有编译器优化会更快.为什么?

无论出于何种原因编译器选择.如果您不让编译器进行优化,那么即使它们具有相同的行为,您也不能指望两个不同的代码具有相似的性能.当启用优化时,编译器可能能够将抽象代码转换为有效代码,并且性能应该是可比较的.

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

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

可能是您的基准测试或编译器中的怪癖.两个功能都具有相同的装配,因此具有相同的性能

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


结论是:

  • C数组并不std::array启用优化时快.至少在使用链接所展示的clang 3.9.1进行编译时是这样.也许你的编译器生成不同的程序集,但我认为它没有理由.

  • 只有在优化之后,C++ 的零成本抽象才是零成本.

  • 编写有意义的微基准并非易事.

  • @JeremyP当然,2D数组在概念上也是一个数组数组.我看到的唯一区别是`std :: array`的两个解引用是在单独的(内联)函数调用中,因此它可能对优化传递的顺序敏感.必须先扩展这些函数,然后才能合并取消引用,而指针的两个解引用甚至在内联之前就已存在.坦率地说,如果装配输出没有(至少几乎)相同,我会感到失望. (2认同)