为何reserve+emplace_back的std::vector比原生数组性能更慢?
关于reserve+emplace_back的std::vector仍比原生数组慢的原因分析
问题背景
已知使用resize或push_back的std::vector会因额外的对象创建/拷贝比原生数组慢,通常建议用reserve+emplace_back提升性能,但实际基准测试发现,即便用了reserve+emplace_back,std::vector仍比原生数组慢很多,请问原因何在?是否存在不公平对比的因素?
测试代码
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <vector> #include <chrono> #include <string> #include <algorithm> #include <climits> using tp = std::chrono::steady_clock::time_point; class Time { public: static void show(tp t1, tp t2) { //time passed since t1 std::cout << std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count() << '\t'; printf("nanoseconds \n"); } tp add() { tp p = std::chrono::steady_clock::now(); return p; } }; int main() { Time time; const int VSIZE = 1000000; auto t1 = time.add(); double vsizearr[VSIZE]; for (auto i = 0; i < VSIZE; i++) { vsizearr[i]=i; asm volatile("" : : : "memory"); //doesn't allow compiler to erase the loop } auto t2 = time.add(); std::vector<double> second; second.reserve(VSIZE); for (auto i = 0; i < VSIZE; i++) { second.emplace_back(i); asm volatile("" : : : "memory"); //doesn't allow compiler to erase the loop } auto t3 = time.add(); time.show(t1, t2); time.show(t2, t3); return 0; }
基准测试结果
Windows 10环境(C++17, LLVM-clang, -O1)
306300 nanoseconds 1824700 nanoseconds
Ubuntu 20.04环境(C17, g/clang, -O1)
root@vlad-VirtualBox:/home/vlad/Documents clang++ -O1 -std=c++17 -o test test.cpp root@vlad-VirtualBox:/home/vlad/Documents ./test 284340 nanoseconds 3115997 nanoseconds root@vlad-VirtualBox:/home/vlad/Documents g++ -O1 -std=c++17 -o test test.cpp root@vlad-VirtualBox:/home/vlad/Documents ./test 284340 nanoseconds 3145702 nanoseconds
注:-O3优化下结果与-O1大致相同。
原因分析
1. 内存分配的本质差异
- 原生数组
double vsizearr[VSIZE]是栈上分配,仅通过调整栈指针完成,几乎无额外开销;而std::vector即便用reserve,也是在堆上分配内存,堆分配需要经过内存管理器的复杂逻辑(如查找空闲块、更新内存链表等),这本身就会带来显著开销。
2. emplace_back的额外状态维护
emplace_back并非单纯的内存赋值:每次调用除了构造基础类型double(本质是赋值),还需要更新vector的size成员变量,这是额外的写内存操作;而原生数组直接操作内存地址,无任何状态维护步骤。
3. 编译器优化的偏向性
- 编译器对栈数组的优化更直接:栈内存地址编译期即可确定,能生成更高效的直接内存访问指令;而
vector的内部数据指针是运行期确定的堆地址,编译器优化时需考虑指针别名等问题,生成的指令效率可能不如栈数组。
4. 基准测试的不公平点
- 测试代码中,
vector的reserve堆分配开销被计入了t2到t3的统计时间内;而原生数组的栈分配开销几乎可以忽略,未对测试结果产生影响。若单独排除reserve的开销,两者的性能差距会有所缩小。
5. 缓存效率差异
- 栈内存属于CPU缓存的热点区域,访问频率高,CPU预取机制更高效;堆内存地址相对随机,缓存命中率可能更低,导致内存访问延迟更高。
总结
核心原因是栈分配与堆分配的本质开销差异,再加上vector内部状态维护的额外操作、编译器优化偏向性以及缓存效率差异共同导致了性能差距。同时测试代码中未剥离reserve的堆分配开销,进一步放大了两者的性能差。
内容的提问来源于stack exchange,提问作者Vladislav
相关产品推荐
相关产品推荐

