C++中二维vector与数组的性能优化探究及疑问
C++大型连续二维数组结构化访问性能测试与疑问
测试背景与环境
我正在优化C++中大型连续二维数组的结构化访问性能,但网上关于最优实现的信息杂乱不一。为此我用以下环境做了四种方案的性能测试:
- 编译器:clang 14.0.3
- 编译参数:
g++ -std=c++20 -O3 vectortest.cpp -o vectortest
测试代码
#include <vector> #include <chrono> #include <iostream> using namespace std::chrono; #define ARRAYSIZE 25000 template <typename T> class vector2d { public: size_t size_x, size_y; std::vector<T> mvec; public: vector2d( size_t a_size_x, size_t a_size_y ) : size_x(a_size_x), size_y(a_size_y) { mvec.resize(size_x * size_y); } public: T & operator()(size_t i, size_t j) { return mvec[i * size_x + j]; } }; template <typename T> class array2d { public: size_t size_x, size_y; T * mvec; public: array2d( size_t a_size_x, size_t a_size_y ) : size_x(a_size_x), size_y(a_size_y) { mvec = new T[size_x * size_y]; } ~array2d() { delete[] mvec; } public: T & operator()(size_t i, size_t j) { return mvec[i * size_x + j]; } }; double vectorvector() { std::vector< std::vector<double> > x(ARRAYSIZE, std::vector<double>(ARRAYSIZE)); for (size_t i = 0; i < ARRAYSIZE; i++) { for (size_t j = 0; j < ARRAYSIZE; j++) { x[i][j] = static_cast<double>(i) * static_cast<double>(j); } } return x[ARRAYSIZE/2][ARRAYSIZE/2]; } double vector_contiguous() { std::vector<double> x(ARRAYSIZE*ARRAYSIZE); for (size_t i = 0; i < ARRAYSIZE; i++) { for (size_t j = 0; j < ARRAYSIZE; j++) { x[i * ARRAYSIZE + j] = static_cast<double>(i) * static_cast<double>(j); } } return x[(ARRAYSIZE/2)*ARRAYSIZE + (ARRAYSIZE/2)]; } double vector2d_class() { vector2d<double> x(ARRAYSIZE, ARRAYSIZE); for (size_t i = 0; i < ARRAYSIZE; i++) { for (size_t j = 0; j < ARRAYSIZE; j++) { x(i,j) = static_cast<double>(i) * static_cast<double>(j); } } return x(ARRAYSIZE/2,ARRAYSIZE/2); } double array2d_class() { array2d<double> x(ARRAYSIZE, ARRAYSIZE); for (size_t i = 0; i < ARRAYSIZE; i++) { for (size_t j = 0; j < ARRAYSIZE; j++) { x(i,j) = static_cast<double>(i) * static_cast<double>(j); } } return x(ARRAYSIZE/2,ARRAYSIZE/2); } int main() { std::chrono::time_point<std::chrono::high_resolution_clock> start, stop; microseconds duration; start = high_resolution_clock::now(); std::cout << vectorvector() << std::endl; stop = high_resolution_clock::now(); duration = duration_cast<microseconds>(stop - start); std::cout << "vectorvector: " << duration.count() << " microseconds" << std::endl; start = high_resolution_clock::now(); std::cout << vector_contiguous() << std::endl; stop = high_resolution_clock::now(); duration = duration_cast<microseconds>(stop - start); std::cout << "vector_contiguous: " << duration.count() << " microseconds" << std::endl; start = high_resolution_clock::now(); std::cout << vector2d_class() << std::endl; stop = high_resolution_clock::now(); duration = duration_cast<microseconds>(stop - start); std::cout << "vector2d_class: " << duration.count() << " microseconds" << std::endl; start = high_resolution_clock::now(); std::cout << array2d_class() << std::endl; stop = high_resolution_clock::now(); duration = duration_cast<microseconds>(stop - start); std::cout << "array2d_class: " << duration.count() << " microseconds" << std::endl; }
测试结果
vectorvector: 3291998 microseconds vector_contiguous: 2238134 microseconds vector2d_class: 2221989 microseconds array2d_class: 1544225 microseconds
疑问解答
1. 这些测试结果是否符合预期?
完全符合预期:
- vectorvector性能最差:它是嵌套vector结构,每个子vector的内存独立分配,物理上不连续。遍历的时候会频繁触发缓存失效——CPU加载完一行数据后,下一行的内存地址可能不在当前缓存页中,需要从内存重新加载,这会极大降低效率,比连续内存方案慢45%是合理的。
- array2d比vector实现更快:虽然vector内部也是连续内存,但编译器对裸指针的优化通常更激进。vector的
operator[]或data()访问需要经过一层成员函数,即便-O3会inline,但裸指针的直接访问减少了潜在的中间操作;另外,new[]分配的内存和vector默认allocator分配的内存可能在对齐策略或分配路径上有差异,导致裸指针版本的内存访问效率更高。
2. 实验存在明显缺陷吗?
有几个值得注意的缺陷:
- 计时包含IO操作:代码中
std::cout放在了计时区间内,IO操作的延迟会严重干扰性能测量,应该先计算并保存结果,计时结束后再输出。 - 单次测试误差大:仅跑一次测试无法排除系统调度、内存碎片化等外界因素的干扰,应该多次运行取平均值。
- 测试场景单一:只测试了写操作,未覆盖读操作、随机访问等场景,不同场景下的性能差异可能不同。
- 数组尺寸影响:25000×25000的数组占用约5GB内存,远超CPU缓存容量,此时内存带宽成为瓶颈,内存连续性的影响被放大。可以尝试更小的数组(能放入L3缓存),观察性能差距是否缩小。
3. 有哪些值得尝试的替代方案?
- C++20 std::mdspan:标准库提供的多维视图,可直接包装一维vector或裸数组,提供结构化的多维访问接口,无额外内存开销,性能接近裸指针。
- 自定义对齐内存分配:使用
aligned_alloc或posix_memalign分配对齐内存(比如64字节对齐适配AVX指令),vector可通过自定义allocator实现对齐,提升缓存命中率和SIMD优化效率。 - 第三方数值库:比如Eigen、Boost.MultiArray,这类库针对多维数组做了深度优化,支持自动SIMD并行、缓存友好的遍历策略,性能远超手动实现。
- std::span包装:用
std::span<std::span<double>>包装连续内存,提供类似二维数组的访问方式,适合C++20及以上版本。
4. STL为何未提供多维vector类型?
主要原因有三点:
- STL组件化设计理念:STL遵循单一职责原则,
vector的定位是动态一维连续数组。多维数组可通过一维vector+自定义访问逻辑实现,无需专门的标准库组件,保持STL的简洁性。 - 需求多样性:多维数组的访问模式(行优先/列优先)、维度数量、内存布局等需求差异大,标准库很难提供一个满足所有场景的通用实现,不如让开发者根据需求自行封装或使用第三方库。
- 标准补充已到位:C++20引入的
std::mdspan已经填补了多维访问的空白,它可以作为一维vector的视图,提供结构化的多维操作,无需单独设计多维vector。
内容的提问来源于stack exchange,提问作者aycarus
相关产品推荐
相关产品推荐

