我们目前正在用C ++编写一些性能关键代码,它们可以在许多大型矩阵和向量上运行。关于我们的研究,std::array
和标准C阵列之间应该没有重大的性能差异
(请参阅This question或this)。
但是,在测试过程中,通过使用std::array
上的C数组,我们获得了巨大的性能提升。
这是我们的演示代码:
#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(¤tTime, 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;
}
编译:{{1}} 这是结果:
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
如果没有编译器优化,C数组实现会快得多。为什么?
使用编译器优化,点积也同样快。但是对于矩阵乘法,使用C数组时仍然存在显着的加速。
使用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
时是否有办法实现同等效果?
更新
编译器使用:std::array
使用icpc 17.0.0
我们的代码运行速度比使用任何优化级别的intel编译器慢得多。因此,我们主要对intel编译器的行为感兴趣。
根据Jonas的建议,我们使用以下结果(英特尔编译器)调整了gcc 4.8.5
:
使用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
答案 0 :(得分:19)
首先,您使用的运行量太少了。就个人而言,我没有意识到(在运行代码之前)你的“持续时间”测量结果是毫秒
通过将RUNS
和DotPSimple
的{{1}}增加到5,000,000,时间如下:
使用C数组进行DotP测试:
持续时间:1074.89
使用C ++数组进行DotP测试:
持续时间:1085.34
也就是说,他们非常接近同样快。事实上,由于基准的随机特性,从测试到测试的速度最快。 DotPComplex
和MatMultSimple
也是如此,但它们只需要50,000次运行。
如果您真的想测量并了解更多信息,您应该接受此基准测试的随机性质,并近似“持续时间”测量的分布。包括函数的随机顺序,以消除任何排序偏差。
编辑: assembly code(来自user2079303的回答)完全证明启用优化没有差异。因此,零成本抽象实际上是零成本且启用了优化,这是一个合理的要求。
<强>更新强>
我使用的编译器:
MatMultComplex
使用以下命令:
g++ (Debian 6.3.0-6) 6.3.0 20170205
使用此处理器:
g++ -Wall -Wextra -pedantic -O3 test.cpp
答案 1 :(得分:10)
为什么......没有编译器优化会更快。为什么呢?
无论编译器选择什么原因。如果您不让编译器进行优化,那么即使它们具有相同的行为,您也不能指望两个不同的代码具有相似的性能。启用优化后,编译器可能能够将抽象代码转换为有效代码,并且性能应具有可比性。
std::array
的使用涉及函数调用,而指针的使用则不然。例如,std::array::operator[]
是一个函数,而指针的下标运算符则不是。进行函数调用可能比不进行函数调用要慢。可以优化所有这些函数调用(扩展内联),但如果您选择不启用优化,则函数调用仍然存在。
但是对于矩阵乘法,使用C数组时仍然存在显着的加速。
可能是您的基准测试或编译器中的怪癖。 Here两个函数具有相同的汇编,因此具有相同的性能
编辑:我同意乔纳斯的回答。基准测试的迭代次数太少。此外,在不重复基准测试和分析偏差的情况下,无法确定两次测量之间的差异是否显着。结论是:
启用优化后,C阵列不比std::array
更快。至少在使用链接所展示的clang 3.9.1进行编译时是这样。也许你的编译器会生成不同的程序集,但我认为没有理由这样做。
只有在优化后,C ++的零成本抽象才是零成本。
编写有意义的微观基准并非易事。