今天我决定对std::vector
和std::array
的gcc优化性的一些差异进行基准测试和比较。通常,我发现了我的预期:在每个短数组上执行任务比在集合等效向量上执行任务要快得多。
但是,我发现了意外的事情:使用std::vector
来存储数组集合更快而不是使用std::array
。为了防止它是堆栈上大量数据的一些工件的结果,我还尝试将它作为一个数组分配在堆上和堆上的C风格数组中(但结果仍然类似于一个数组堆栈上的数组和数组的向量。)
知道为什么std::vector
永远优于std::array
(编译器有更多编译时信息)?
我使用gcc-4.7 -std=c++11 -O3
编译(gcc-4.6 -std=c++0x -O3
也应该导致这个难题)。运行时使用bash
- 本机time
命令(用户时间)计算。
代码:
#include <array>
#include <vector>
#include <iostream>
#include <assert.h>
#include <algorithm>
template <typename VEC>
double fast_sq_dist(const VEC & lhs, const VEC & rhs) {
assert(lhs.size() == rhs.size());
double result = 0.0;
for (int k=0; k<lhs.size(); ++k) {
double tmp = lhs[k] - rhs[k];
result += tmp * tmp;
}
return result;
}
int main() {
const std::size_t K = 20000;
const std::size_t N = 4;
// declare the data structure for the collection
// (uncomment exactly one of these to time it)
// array of arrays
// runtime: 1.32s
std::array<std::array<double, N>, K > mat;
// array of arrays (allocated on the heap)
// runtime: 1.33s
// std::array<std::array<double, N>, K > & mat = *new std::array<std::array<double, N>, K >;
// C-style heap array of arrays
// runtime: 0.93s
// std::array<double, N> * mat = new std::array<double, N>[K];
// vector of arrays
// runtime: 0.93
// std::vector<std::array<double, N> > mat(K);
// vector of vectors
// runtime: 2.16s
// std::vector<std::vector<double> > mat(K, std::vector<double>(N));
// fill the collection with some arbitrary values
for (std::size_t k=0; k<K; ++k) {
for (std::size_t j=0; j<N; ++j)
mat[k][j] = k*N+j;
}
std::cerr << "constructed" << std::endl;
// compute the sum of all pairwise distances in the collection
double tot = 0.0;
for (std::size_t j=0; j<K; ++j) {
for (std::size_t k=0; k<K; ++k)
tot += fast_sq_dist(mat[j], mat[k]);
}
std::cout << tot << std::endl;
return 0;
}
NB 1:所有版本都打印相同的结果。
NB 2:只是为了证明std::array<std::array<double, N>, K>
,std::vector<std::array<double, N> >
和std::vector<std::vector<double> >
之间的运行时差异并非仅仅来自分配/初始化在分配时,简单分配集合的运行时(即注释掉tot
的计算和打印)分别为0.000s,0.000s和0.004s。
NB 3:每个方法都是单独编译和运行的(不是在同一个可执行文件中背靠背定时),以防止缓存中的不公平差异。
NB 4:
数组数组的汇编:http://ideone.com/SM8dB
数组矢量的汇编:http://ideone.com/vhpJv
矢量矢量装配:http://ideone.com/RZTNE
NB 5:为了绝对清楚,我绝不打算批评STL。绝对喜欢STL,不仅经常使用它,有效使用的细节也教会了我很多C ++的微妙和强大功能。相反,这是一种智力上的追求:我只是为了学习有效的C ++设计原则。
此外,归咎于STL是不合理的,因为很难对运行时差异的病因进行去卷积:启用优化后,可以通过编译器优化来减慢代码而不是加速它。关闭优化后,它可以来自不必要的复制操作(可以优化并且永远不会在生产代码中执行),这些操作可能比某些数据类型更偏向某些数据类型。
如果你像我一样好奇,我会非常乐意帮助您解决这个问题。
答案 0 :(得分:3)
考虑第二次和第三次测试。从概念上讲,它们是相同的:从堆中分配K * N * sizeof(double)
个字节,然后以完全相同的方式访问它们。那么为什么不同的时代?
所有“更快”的测试都有一个共同点:new[]
。所有较慢的测试都分配有new
或堆栈。 vector
可能会使用new[]
Under the Hood™。造成这种情况的唯一明显原因是new[]
和new
的实现与预期的实现差异更大。
我要建议的是new[]
将回退到mmap
并直接在页面边界上分配,为您提供对齐加速,而其他两种方法不会分配到页面边界。
考虑使用操作系统分配功能直接映射已提交的页面,然后将std::array<std::array<double, N>, K>
放入其中。
答案 1 :(得分:3)
我怀疑在堆栈或堆上分配array
时,编译器只需对齐array
,而在使用vector
的分配器时,它可能使用operator new
必须返回适合任何类型的内存。如果分配的内存碰巧更好地对齐,允许更多的缓存命中/更大的读取,那么这似乎可以很容易地解释性能差异。
答案 2 :(得分:1)
当简单的解释足够时,不要搜索复杂的解释。这是一个优化错误。普通的旧固定大小的C风格堆栈分配数组提供与std::array
类似的性能,因此不要责怪std::array
实现。
答案 3 :(得分:1)
我刚刚在桌面上使用MSVC ++ 2010进行了尝试,除了vector
的{{1}}之外的所有测试都获得了相同的时间(1.6秒),这是5.0秒。
我会考虑查看您的图书馆vectors
和array
的实际实施情况,看看是否存在明显差异。
尝试用迭代器样式的循环替换索引样式的循环,看看它是否会影响性能。
另外,尝试使用vector
从程序中计算程序的时间:除此之外,这将让您告诉代码的哪一部分采取不同的行为。它甚至可能值得在嵌套范围中添加,因此您也可以为对象析构函数计时。
答案 4 :(得分:0)
我想到的一件事是,一次性堆栈中的这样一个大对象可能会触发操作系统重新分配堆栈空间。尝试在主
的末尾转储/ proc / self / maps答案 5 :(得分:0)
我看到的唯一重大区别是您的数据存储方式不同。在前两种情况下,您的数据存储在一个巨大的块中。所有其他情况都存储指向矩阵中行的指针。我不太清楚为什么这会使你的代码更快,但它可能与查找和CPU预取有关。在迭代之前尝试缓存矩阵行,这样您就不需要为每个条目查找mat[k]
。这可以使它更快,甚至速度更快。可能是您的编译器可以在vector<array<T>>
情况下执行此操作,但不能在array<array<T>>
情况下执行此操作。