C++ 中返回容器的性能对比:RVO、Move、Copy 与 Copy + const&
在 C++ 中,函数返回一个容器(如 std::vector)时,不同的返回方式在性能上差异很大。本文通过一个简单 benchmark,比较了四种典型方式的性能差异:
- RVO / NRVO
- 手动拷贝
- 手动 std::move
- 手动拷贝 const&
同时,代码在输出中使用科学计数法显示容器大小,便于大数据量场景下查看。
测试环境
| 项目 | 说明 |
|---|---|
| 架构 | x86_64 |
| CPU 运行模式 | 32-bit, 64-bit |
| 字节序 | Little Endian |
| Address sizes | 43 bits physical, 48 bits virtual |
| CPU 核心数 | 16 |
| 每个核线程数 | 2 |
| CPU 座数 | 1 |
| NUMA 节点 | 1 |
| 厂商 ID | HygonGenuine |
| CPU 系列 | 24 |
| 型号名称 | Hygon C86-3G (OPN:3350) |
| CPU MHz | 2996.687 MHz |
| 缓存 L1d | 256 KiB |
| 缓存 L1i | 512 KiB |
| 缓存 L2 | 4 MiB |
| 缓存 L3 | 16 MiB |
| 虚拟化 | AMD-V |
| 操作系统 | Linux version 5.4.18-142-generic |
| 内存 | 32GB |
| 编译器 | GCC 9.4.0 (Ubuntu 9.4.0-1kylin1~20.04.2) |
| Spectre v1 | Vulnerable |
| Spectre v2 | Mitigation; Retpolines, IBPB conditional, STIBP disabled |
| 其它主要漏洞 | Not affected |
| CPU 特性标记(部分) | fpu, sse, sse2, ht, syscall, avx, avx2, bmi1, bmi2, fma, … |
CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
project(test)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#if(NOT CMAKE_BUILD_TYPE)
# set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
#endif()
message(STATUS "CMAKE_BUILD_TYPE : " ${CMAKE_BUILD_TYPE})
message(STATUS "CXX flags for this build type: ${CMAKE_CXX_FLAGS}")
# 自定义 Release 编译选项(可选)
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG -march=native -Wall")
message(STATUS "CXX flags for this build type: ${CMAKE_CXX_FLAGS_RELEASE}")
include_directories(boost)
find_package(Threads REQUIRED)
add_executable(test main.cpp ChannelConfig.h LineReader/LineReader.h LineReader/LineReader.cpp)
target_link_libraries(test PUBLIC Threads::Threads) # 跨平台 支持线程库
Benchmark 代码
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip> // 用于科学计数法输出
constexpr size_t N = 10'000'000;
constexpr size_t LOOP = 100;
// ---------- Case 1: RVO ----------
std::vector<int> create_vector_rvo() {
return std::vector<int>(N, 42); // 触发 NRVO / mandatory RVO (C++17)
}
// ---------- Case 2: 手动拷贝 ----------
std::vector<int> create_vector_copy() {
std::vector<int> v(N, 42);
std::vector<int> tmp = v; // 拷贝
return tmp;
}
// ---------- Case 3: 手动 std::move ----------
std::vector<int> create_vector_move() {
std::vector<int> v(N, 42);
return std::move(v); // move
}
// ---------- Case 4: 手动拷贝 const& ----------
std::vector<int> create_vector_copy_const_ref() {
std::vector<int> v(N, 42);
const std::vector<int>& cref = v;
std::vector<int> tmp = cref; // 拷贝
return tmp;
}
// Benchmark 测试函数
template<typename Func>
void run_test(const std::string &name, Func f) {
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < LOOP; ++i) {
auto v = f();
volatile int x = v[0]; // 防止优化掉
(void)x;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << name << " : " << duration << " ms\n";
}
int main() {
std::cout << std::scientific << std::setprecision(2);
std::cout << "Vector size: " << static_cast<double>(N)
<< ", loop: " << static_cast<double>(LOOP) << "\n\n";
run_test("RVO", create_vector_rvo);
run_test("Copy", create_vector_copy);
run_test("Move", create_vector_move);
run_test("copy_const_ref", create_vector_copy_const_ref);
return 0;
}
实验说明
- 优化等级:
O0 - Vector 大小:
N = 10,000,000 - 循环次数:
LOOP = 100 - 测试环境:Release 模式 (
-O2或-O3) - 防止优化掉:使用
volatile int x = v[0];保证编译器不会优化掉容器构造。 - 输出格式:科学计数法显示
Vector size和LOOP,便于大数据量阅读。
实验结果(示例)
注:实际数值可能因机器、编译器和优化级别而不同
优化等级 O0 (Debug 模式)
Vector size: 1.00e+07, loop: 1.00e+02
RVO : 4890 ms
Copy : 5532 ms
Move : 4815 ms
copy_const_ref : 5510 ms
优化等级 O2 (Release 模式)
Vector size: 1.00e+07, loop: 1.00e+02
RVO : 750 ms
Copy : 1742 ms
Move : 746 ms
copy_const_ref : 1742 ms
优化等级 O3 (Release 模式)
Vector size: 1.00e+07, loop: 1.00e+02
RVO : 513 ms
Copy : 1175 ms
Move : 508 ms
copy_const_ref : 1166 ms
分析
-
RVO / NRVO
- 编译器直接在调用方栈上构造返回对象。
- 理论上零拷贝,性能最佳。
- 在 C++17 后,RVO 是强制性的。
-
Move
std::move会触发 move 构造。- 对
std::vector来说,move 是 O(1),只交换指针和容量。 - 略慢于 RVO,但比拷贝快很多。
-
Copy
- 发生 N 个元素的拷贝操作。
- 时间复杂度 O(N),对于大容器性能开销明显。
- Release 模式下仍然比 RVO / move 慢数倍。
-
Copy + const&
- 本质上也是拷贝元素。
- 对比纯 copy,性能几乎一致。
- 在某些场景下可以用于保留原容器,但开销大。
结论
- 优先使用 RVO / NRVO,返回局部对象即可。
- Move 适用于返回函数参数或在某些条件下无法使用 RVO。
- Copy 或 Copy + const& 除非必须,否则应尽量避免大容器的拷贝。
补充建议
- 不要手动
std::move局部变量返回,可能会阻止 NRVO。 - 科学计数法便于输出大数组或循环次数,避免数字过长难以阅读。
- 对性能敏感的场景,建议结合 benchmark 真实测量,不仅依赖理论分析。

浙公网安备 33010602011771号