具有固定大小阵列的STL算法的效率

时间:2014-03-24 22:29:44

标签: c++ arrays algorithm stl

一般来说,我认为任何算法的STL实现至少与我能提出的任何算法一样有效(具有无错误的额外好处)。但是,我开始怀疑STL对迭代器的关注在某些情况下是否有害。

让我们假设我想计算两个固定大小数组的内积。我天真的实现看起来像这样:

std::array<double, 100000> v1;
std::array<double, 100000> v2;
//fill with arbitrary numbers

double sum = 0.0;
for (size_t i = 0; i < v1.size(); ++i) {
    sum += v1[i] * v2[i];
}

由于在编译期间已知迭代次数和内存布局,并且所有操作都可以直接映射到本机处理器指令,因此编译器应该能够轻松地从中生成“最佳”机器代码(循环展开,向量化) / FMA说明......)。

STL版

double sum = std::inner_product(cbegin(v1), cend(v1), cbegin(v2), 0.0);
另一方面,

增加了一些额外的间接性,即使所有内容都被内联,编译器仍然必须推断出它正在连续的内存区域以及该区域所在的位置。虽然原则上这肯定是可能的,但我想知道,典型的c ++编译器是否真的会这样做。

所以我的问题是:您认为,实现在我自己的固定大小阵列上运行的标准算法可以带来性能优势,还是STL版本总是优于手动实现?

1 个答案:

答案 0 :(得分:1)

根据建议,我做了一些测量并且

  • 代码如下
  • 使用VS2013 for x64在发布模式下编译
  • 在带有i7-2640M的Win8.1机器上执行,

算法版本持续减慢约20%(15.6-15.7s vs 12.9-13.1s)。 NREPS的相对差异也大致保持两个数量级。

所以我猜答案是:使用标准库算法会影响性能。

如果这是一个普遍的问题,或者它是特定于我的平台,编译器和基准测试,那仍然会很有趣。欢迎您发布自己的结果或评论基准。

#include <iostream>
#include <numeric>
#include <array>
#include <chrono>
#include <cstdlib>

#define USE_STD_ALGORITHM

using namespace std;
using namespace std::chrono;

static const size_t N = 10000000; //size of the arrays
static const size_t REPS = 1000; //number of repitions

array<double, N> a1;
array<double, N> a2;

int main(){
    srand(10);
    for (size_t i = 0; i < N; ++i) {
        a1[i] = static_cast<double>(rand())*0.01;
        a2[i] = static_cast<double>(rand())*0.01;
    }

    double res = 0.0;
    auto start=high_resolution_clock::now();
    for (size_t z = 0; z < REPS; z++) {     
        #ifdef USE_STD_ALGORITHM
            res = std::inner_product(a1.begin(), a1.end(), a2.begin(), res);        
        #else           
            for (size_t t = 0; t < N; ++t)  {
                res+= a1[t] * a2[t];
            }
        #endif        
    }
    auto end = high_resolution_clock::now();

    std::cout << res << "  "; // <-- necessary, so that loop isn't optimized away
    std::cout << duration_cast<milliseconds>(end - start).count() <<" ms"<< std::endl;

}
/* 
 * Update: Results (ubuntu 14.04 , haswell)
 * STL: algorithm
 * g++-4.8-2    -march=native -std=c++11 -O3 main.cpp               : 1.15287e+24  3551 ms
 * g++-4.8-2    -march=native -std=c++11 -ffast-math -O3 main.cpp   : 1.15287e+24  3567 ms
 * clang++-3.5  -march=native -std=c++11 -O3 main.cpp               : 1.15287e+24  9378 ms
 * clang++-3.5  -march=native -std=c++11 -ffast-math -O3 main.cpp   : 1.15287e+24  8505 ms
 *
 * loop:
 * g++-4.8-2    -march=native -std=c++11 -O3 main.cpp               : 1.15287e+24  3543 ms
 * g++-4.8-2    -march=native -std=c++11 -ffast-math -O3 main.cpp   : 1.15287e+24  3551 ms
 * clang++-3.5  -march=native -std=c++11 -O3 main.cpp               : 1.15287e+24  9613 ms
 * clang++-3.5  -march=native -std=c++11 -ffast-math -O3 main.cpp   : 1.15287e+24  8642 ms
 */  

编辑:
我在同一台机器上的fedora 21 Virtual Box VM上用g ++ - 4.9.2和clang ++ - 3.5与O3std=c++11进行了快速检查,显然这些编译器没有相同的问题(两个版本的时间几乎相同)。然而,gcc的版本大约是clang的两倍(7.5s vs 14s)。