C++ 中返回容器的性能对比:RVO、Move、Copy 与 Copy + const&

在 C++ 中,函数返回一个容器(如 std::vector)时,不同的返回方式在性能上差异很大。本文通过一个简单 benchmark,比较了四种典型方式的性能差异:

  1. RVO / NRVO
  2. 手动拷贝
  3. 手动 std::move
  4. 手动拷贝 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

分析

  1. RVO / NRVO

    • 编译器直接在调用方栈上构造返回对象。
    • 理论上零拷贝,性能最佳。
    • 在 C++17 后,RVO 是强制性的。
  2. Move

    • std::move 会触发 move 构造。
    • 对 std::vector 来说,move 是 O(1),只交换指针和容量。
    • 略慢于 RVO,但比拷贝快很多。
  3. Copy

    • 发生 N 个元素的拷贝操作。
    • 时间复杂度 O(N),对于大容器性能开销明显。
    • Release 模式下仍然比 RVO / move 慢数倍。
  4. Copy + const&

    • 本质上也是拷贝元素。
    • 对比纯 copy,性能几乎一致。
    • 在某些场景下可以用于保留原容器,但开销大。

结论

  • 优先使用 RVO / NRVO,返回局部对象即可。
  • Move 适用于返回函数参数或在某些条件下无法使用 RVO。
  • Copy 或 Copy + const& 除非必须,否则应尽量避免大容器的拷贝。

补充建议

  1. 不要手动 std::move 局部变量返回,可能会阻止 NRVO。
  2. 科学计数法便于输出大数组或循环次数,避免数字过长难以阅读。
  3. 对性能敏感的场景,建议结合 benchmark 真实测量,不仅依赖理论分析。
posted @ 2026-02-12 13:30  BlackSnow  阅读(62)  评论(0)    收藏  举报