前置知识: C++

C++性能优化

00:00
3 min Advanced 2026/6/14

C++性能优化技巧

概述

性能优化是 C++ 开发中的重要课题。C++ 之所以被广泛用于系统编程和高性能计算,正是因为它提供了对硬件的精细控制能力。性能优化并非盲目地调整代码,而是遵循”测量-分析-优化”的循环:先通过性能分析工具定位瓶颈,再针对性地优化,最后验证优化效果。

过早优化是万恶之源,但关键路径上的性能问题必须认真对待。理解编译器优化、内存层次结构和算法复杂度是进行有效优化的基础。

基础概念

性能优化的层次

层次说明影响程度
算法与数据结构选择合适的算法量级提升
内存访问模式缓存友好的数据布局数倍提升
编译器优化利用编译器能力10%-50%
并行多线程/向量化心数成正比
平台特定优化SIMD/内联汇编特定场景显著

性能分析工具

工具说明
perfLinux 性能分析神器
VTuneIntel 出品的综合性能分析器
gprofGCC 自带的采样分析器
Valgrind/Callgrind缓存调用分析
Tracy实时分析,适合游戏开发
Google Benchmark基准测试框架

快速上手

编译器优化选项

# GCC/Clang 常用优化选项
-O2              # 标准优化,大多数项目的推荐级别
-O3              # 激进优化,可能增加代码体积
-Os             # 优化代码体积,适合嵌入式
-march=native    # 针对本机 CPU 指令集优化
-flto            # 链接时优化(Link Time Optimization)
-fprofile-generate / -fprofile-use  # PGO(配置文件引导优化)

使用 Google Benchmark

#include <benchmark/benchmark.h>

// 被测函数
int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

// 定义基准测试
static void BM_Fibonacci(benchmark::State& state) {
    for (auto _ : state) {
        int result = fibonacci(state.range(0));
        benchmark::DoNotOptimize(result);  // 防止编译器优化掉结果
    }
}

// 注册测试,参数为 fibonacci 的输入
BENCHMARK(BM_Fibonacci)->Arg(10)->Arg(20)->Arg(30);

BENCHMARK_MAIN();
# 编译并运行
g++ -O2 benchmark_test.cpp -lbenchmark -lpthread -o bench
./bench

详细用法

数据布局优化

// 方式一:Array of Structures(AoS)
struct PointAoS {
    float x, y, z;
};
std::vector<PointAoS> points_aos;

// 方式二:Structure of Arrays(SoA)
struct PointSoA {
    std::vector<float> x, y, z;  // 每个分量连续存储
};

// SoA 更适合 SIMD 和缓存行
// 当只需要处理 x 分量时,SoA 的缓存命中率远高于 AoS
// 因为 AoS 中 y 和 z 占用了缓存行空间但不需要

// 示例:对 x 分量求和
float sumX_AoS(const std::vector<PointAoS>& points) {
    float sum = 0;
    for (const auto& p : points) {
        sum += p.x;  // 每次访问间隔 12 字节,缓存利用率低
    }
    return sum;
}

float sumX_SoS(const PointSoA& points) {
    float sum = 0;
    for (float x : points.x) {
        sum += x;  // 连续内存访问,缓存利用率高
    }
    return sum;
}

移动语义减少拷贝

#include <vector>
#include <string>
#include <utility>

class DataBuffer {
    std::vector<int> data_;
    std::string name_;
public:
    // 移动构造函数,O(1) 而非 O(n)
    DataBuffer(DataBuffer&& other) noexcept
        : data_(std::move(other.data_))
        , name_(std::move(other.name_)) {}

    DataBuffer& operator=(DataBuffer&& other) noexcept {
        if (this != &other) {
            data_ = std::move(other.data_);
            name_ = std::move(other.name_);
        }
        return *this;
    }

    // 返回值优化(RVO/NRVO)
    static DataBuffer createLargeBuffer(size_t size) {
        DataBuffer buf;
        buf.data_.resize(size);
        return buf;  // 编译器直接在调用方栈上构造,零拷贝
    }
};

字符串优化

#include <string>
#include <string_view>
#include <sstream>

// 使用 string_view 避免字符串拷贝
std::string processName(const std::string& name) {
    // 每次传参可能产生临时 string
}

std::string processName(std::string_view name) {
    // string_view 不拥有数据,零拷贝传参
    // 适用于只读场景
}

// 使用 reserve 避免反复分配
std::string buildMessage(const std::vector<std::string>& parts) {
    std::string result;
    size_t total = 0;
    for (const auto& p : parts) total += p.size();
    result.reserve(total);  // 一次性分配足够空间

    for (const auto& p : parts) {
        result += p;  // 不会触发重新分配
    }
    return result;
}

避免不必要的分配

#include <vector>

// 复用容器避免反复分配
class DataProcessor {
    std::vector<int> buffer_;  // 成员变量,跨调用复用
public:
    void process(const std::vector<int>& input) {
        buffer_.clear();              // 清空内容但不释放内存
        buffer_.reserve(input.size()); // 确保容量足够
        // 处理逻辑...
        for (int val : input) {
            if (val > 0) buffer_.push_back(val * 2);
        }
    }
};

// 使用 emplace_back 代替 push_back
std::vector<std::string> names;
names.emplace_back("张三");     // 直接在容器内构造
names.push_back("李四");        // 先构造临时对象再移动/拷贝

常见场景

热路径优化

// 优化前:频繁的 map 查找
void processHotPath(const std::map<std::string, int>& config) {
    for (int i = 0; i < 1000000; ++i) {
        auto it = config.find("threshold");  // 每次红黑树查找 O(log n)
        if (it != config.end() && i > it->second) {
            // 处理逻辑
        }
    }
}

// 优化后:提前提取值
void processHotPathOptimized(const std::map<std::string, int>& config) {
    int threshold = config.at("threshold");  // 只查找一次
    for (int i = 0; i < 1000000; ++i) {
        if (i > threshold) {  // 直接比较,O(1)
            // 处理逻辑
        }
    }
}

IO 密集型优化

#include <iostream>
#include <fstream>

// 优化前:逐字符输出
void writeSlow(const std::vector<int>& data) {
    for (int val : data) {
        std::cout << val << "\n";  // 每次 << 都可能刷新缓冲区
    }
}

// 优化后:批量写入
void writeFast(const std::vector<int>& data) {
    std::ofstream file("output.txt");
    file.rdbuf()->pubsetbuf(nullptr, 1 << 16);  // 设置大缓冲区

    std::string line;
    line.reserve(32);
    for (int val : data) {
        line = std::to_string(val);
        line += '\n';
        file.write(line.data(), line.size());
    }
}

注意事项

  • 优化前必须先测量,使用性能分析工具定真正的瓶颈,不要凭直觉优化
  • 编译器优化能力很强,很微观优化(如手动内联编译器已经自动完成
  • noexcept 可以让编译器生成更优的代码(移动操作应始终标记 noexcept
  • 优化会降低代码可读性,应通过基准测试验证每次优化的实际收益
  • 注意优化可能引入的副作用,如缓存一致性线程安全问题
  • 不同平台编译器优化效果可能不同,跨平台项目应在目标平台上验证

进阶用法

SIMD 向量化

#include <immintrin.h>

// 使用 AVX2 向量化浮点运算
void addVectorsAVX2(const float* a, const float* b, float* result, size_t count) {
    size_t i = 0;
    // 每次处理 8 个 float(256 位 AVX2)
    for (; i + 7 < count; i += 8) {
        __m256 va = _mm256_loadu_ps(&a[i]);
        __m256 vb = _mm256_loadu_ps(&b[i]);
        __m256 vr = _mm256_add_ps(va, vb);
        _mm256_storeu_ps(&result[i], vr);
    }
    // 处理剩余元素
    for (; i < count; ++i) {
        result[i] = a[i] + b[i];
    }
}

内存池

#include <vector>
#include <cstddef>

// 固定大小对象的内存池
template<typename T, size_t BlockSize = 4096>
class MemoryPool {
    struct Block {
        alignas(T) char data[BlockSize * sizeof(T)];
        Block* next;
    };

    std::vector<Block*> blocks_;
    size_t index_ = 0;

public:
    T* allocate() {
        if (index_ == 0 || index_ >= BlockSize) {
            auto* block = new Block{};
            block->next = nullptr;
            blocks_.push_back(block);
            index_ = 0;
        }
        size_t block_idx = blocks_.size() - 1;
        return reinterpret_cast<T*>(&blocks_[block_idx]->data[index_++ * sizeof(T)]);
    }

    ~MemoryPool() {
        for (auto* block : blocks_) delete block;
    }
};

PGO 配置文件引导优化

# 步骤 1: 编译插桩版本
g++ -O2 -fprofile-generate=./profile_data main.cpp -o app_instrumented

# 步骤 2: 运行典型工作负载,收集性能数据
./app_instrumented < typical_input.txt

# 步骤 3: 使用收集的数据重新编译
g++ -O2 -fprofile-use=./profile_data main.cpp -o app_optimized

# PGO 可以让编译器根据实际运行时的分支概率进行更精确的优化

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式