C++ Core Guidelines 阅读笔记(5):性能与并发
发布于:
前言
本文是 C++ Core Guidelines 阅读笔记的第五篇,重点讨论性能优化和并发编程的规范。C++ 的优势在于性能,但需要正确使用才能发挥出来。
1. 性能优化原则
性能优化需要遵循正确的原则,避免过早优化和错误优化。
1.1 先测量,再优化
性能优化的黄金法则:先测量,找到真正的瓶颈,再优化。
// 好的:先测量性能
void optimize() {
auto start = std::chrono::high_resolution_clock::now();
process_data();
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
end - start);
std::cout << "Time: " << duration.count() << " us\n";
// 分析性能瓶颈,找到真正的热点
}
// 不好:过早优化
void optimize() {
// 没有测量就优化,可能优化了错误的地方
// 浪费时间和精力,代码变得更复杂
}
1.2 避免不必要的拷贝
拷贝是常见的性能瓶颈,应该尽量避免:
// 好的:使用引用
void process(const std::vector<int>& vec);
// 无拷贝开销
// 不好:值传递
void process(std::vector<int> vec);
// 拷贝整个 vector,开销大
1.3 使用移动语义
移动语义可以避免昂贵的拷贝操作:
// 好的:移动语义
std::vector<int> create_data() {
std::vector<int> data;
// ...
return data; // 移动,不拷贝
}
// 不好:不必要的拷贝
std::vector<int> data = create_data();
// 可能拷贝(如果编译器没有优化)
2. 内存管理性能
2.1 预分配容器空间
// 好的:预分配空间
std::vector<int> vec;
vec.reserve(1000); // 避免多次重新分配
for (int i = 0; i < 1000; ++i) {
vec.push_back(i);
}
// 不好:动态分配
std::vector<int> vec;
for (int i = 0; i < 1000; ++i) {
vec.push_back(i); // 可能多次重新分配
}
2.2 使用对象池
// 好的:对象池
class ObjectPool {
private:
std::vector<std::unique_ptr<Object>> pool_;
public:
std::unique_ptr<Object> acquire() {
if (pool_.empty()) {
return std::make_unique<Object>();
}
auto obj = std::move(pool_.back());
pool_.pop_back();
return obj;
}
void release(std::unique_ptr<Object> obj) {
pool_.push_back(std::move(obj));
}
};
3. 算法选择
3.1 选择合适的算法
// 好的:选择合适的算法
std::sort(vec.begin(), vec.end()); // O(n log n)
// 如果只需要部分排序
std::partial_sort(vec.begin(), vec.begin() + 10, vec.end());
// 如果只需要第 k 大元素
std::nth_element(vec.begin(), vec.begin() + k, vec.end());
3.2 避免不必要的算法调用
// 好的:直接访问
if (!vec.empty()) {
int first = vec[0];
}
// 不好:使用算法
auto it = std::find_if(vec.begin(), vec.end(), [](int x) { return true; });
if (it != vec.end()) {
int first = *it;
}
4. 并发编程
4.1 使用标准库线程
// 好的:标准库线程
#include <thread>
void worker() {
// 工作逻辑
}
std::thread t(worker);
t.join();
4.2 使用互斥锁保护共享数据
// 好的:使用互斥锁
std::mutex mtx;
int shared_data = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
}
// 不好:无保护访问
int shared_data = 0;
void increment() {
++shared_data; // 数据竞争
}
4.3 避免死锁
// 好的:按固定顺序获取锁
void process() {
std::lock(mtx1, mtx2); // 同时获取多个锁
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}
// 不好:不同顺序获取锁
void process1() {
mtx1.lock();
mtx2.lock(); // 可能死锁
}
void process2() {
mtx2.lock();
mtx1.lock(); // 不同顺序
}
5. 原子操作
5.1 使用原子类型
// 好的:原子类型
std::atomic<int> counter{0};
void increment() {
counter.fetch_add(1);
}
// 不好:非原子操作
int counter = 0;
void increment() {
++counter; // 数据竞争
}
5.2 内存序
// 好的:明确内存序
std::atomic<int> data{0};
std::atomic<bool> ready{false};
// 线程 1
data.store(42, std::memory_order_release);
ready.store(true, std::memory_order_release);
// 线程 2
if (ready.load(std::memory_order_acquire)) {
int value = data.load(std::memory_order_acquire);
}
6. 条件变量
6.1 使用条件变量同步
// 好的:条件变量
std::condition_variable cv;
std::mutex mtx;
bool ready = false;
void producer() {
std::lock_guard<std::mutex> lock(mtx);
ready = true;
cv.notify_one();
}
void consumer() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return ready; });
}
7. 异步编程
7.1 使用 std::async
// 好的:std::async
auto future = std::async(std::launch::async, []() {
return compute_result();
});
int result = future.get();
7.2 使用 std::future
// 好的:std::future
std::promise<int> promise;
auto future = promise.get_future();
std::thread t([&promise]() {
promise.set_value(42);
});
int result = future.get();
t.join();
8. 性能测量
8.1 使用性能分析工具
// 使用 perf 分析
// perf record ./program
// perf report
// 使用 gprof
// 编译时添加 -pg
// gprof ./program gmon.out
8.2 基准测试
// 好的:基准测试
void benchmark() {
const int iterations = 1000000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
process();
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
end - start);
std::cout << "Average: " << duration.count() / iterations << " us\n";
}
9. 最佳实践
9.1 性能优化检查清单
- 先测量,再优化
- 避免不必要的拷贝
- 使用移动语义
- 预分配容器空间
- 选择合适的算法
- 使用并发提高性能
- 保护共享数据
- 避免死锁
9.2 常见性能问题
// 问题 1:不必要的拷贝
void process(std::vector<int> vec); // 拷贝开销
// 问题 2:频繁内存分配
for (int i = 0; i < n; ++i) {
vec.push_back(i); // 可能多次重新分配
}
// 问题 3:数据竞争
int counter = 0;
void increment() {
++counter; // 多线程不安全
}
11. 实际工程案例
11.1 案例:性能优化
问题:程序运行慢
// 原始代码:性能差
void process_data(std::vector<int> data) { // 拷贝
for (int x : data) { // 范围 for
process(x);
}
}
改进:优化参数传递和算法
// 改进代码:性能好
void process_data(const std::vector<int>& data) { // 引用传递
std::for_each(data.begin(), data.end(), process); // 算法
}
11.2 案例:并发优化
问题:单线程处理慢
// 原始代码:单线程
void process_all(const std::vector<Data>& items) {
for (const auto& item : items) {
process(item); // 串行处理
}
}
改进:使用多线程
// 改进代码:多线程
void process_all(const std::vector<Data>& items) {
std::vector<std::thread> threads;
const size_t num_threads = std::thread::hardware_concurrency();
for (size_t i = 0; i < num_threads; ++i) {
threads.emplace_back([&items, i, num_threads]() {
for (size_t j = i; j < items.size(); j += num_threads) {
process(items[j]);
}
});
}
for (auto& t : threads) {
t.join();
}
}
12. 性能分析工具
12.1 性能分析工具
12.2 性能指标
// 关键性能指标
struct PerformanceMetrics {
double cpu_time; // CPU 时间
double wall_time; // 墙上时钟时间
size_t memory_usage; // 内存使用
size_t cache_misses; // 缓存未命中
size_t branch_misses; // 分支预测失败
};
13. 并发设计模式
13.1 生产者-消费者模式
13.2 线程池模式
14. 最佳实践检查清单
14.1 性能优化检查清单
- 先测量,再优化
- 避免不必要的拷贝
- 使用移动语义
- 预分配容器空间
- 选择合适的算法
- 使用并发提高性能
- 保护共享数据
- 避免死锁
14.2 并发编程检查清单
- 使用互斥锁保护共享数据
- 避免数据竞争
- 避免死锁
- 使用原子操作进行简单同步
- 使用条件变量进行线程同步
- 合理使用线程池
15. 小结
性能优化和并发编程需要遵循正确的原则和模式。
核心要点:
- 性能优化原则:先测量再优化、避免不必要的拷贝、使用移动语义
- 内存管理性能:预分配空间、使用对象池
- 算法选择:选择合适的算法、避免不必要的调用
- 并发编程:使用标准库线程、互斥锁保护数据、避免死锁
- 原子操作:使用原子类型、明确内存序
- 条件变量:使用条件变量同步线程
- 异步编程:使用 std::async 和 std::future
- 性能测量:使用性能分析工具、基准测试
遵循这些规范,可以写出高性能、线程安全的 C++ 代码。