0. 引言
最近在使用 perf 进行性能分析时,发现串口数据业务代码解析占用大量CPU资源。具体是 DataRecv 函数读取串口数据并通过 ProtocolParse 类解析帧,其中频繁的内存分配与释放是一个瓶颈:每次解析新帧时都会动态分配和释放新的对象。
而应用对象池(Object Pool)通过预分配和复用对象,可减少内存操作开销,提升系统响应速度并减少内存碎片。
本文将简单介绍对象池的使用。
关于如何使用 perf 进行性能分析,i请查看我的另一篇文章:使用perf(火焰图)查看热点函数和系统调用最大延迟函数
1. 实施对象池的目的
对象池设计模式的核心思想是在程序启动时预先创建一组对象,并将其存储在一个池中。当应用程序需要新对象时,它会从池中取出一个已存在的对象,而不是创建一个新的。使用完毕后,对象被放回池中,可供后续使用。这一过程避免了昂贵的构造和析构操作,同时减少了内存碎片问题。
具体目的是:
- 减少内存分配与释放开销:频繁的内存操作是性能瓶颈之一,对象池通过预分配减少了这种开销。
- 提高系统响应速度:通过减少垃圾收集和内存管理的负担,对象池提升了系统性能。
- 避免内存碎片:对象池通过统一管理对象的生命周期,有效防止了内存碎片的产生。
2. 原始代码与性能瓶颈
基于我的业务代码抽象出一个简单的数据处理类DataProcessor,在没有对象池的情况下,我们的代码可能是这样的:
#include <iostream>
#include <vector>
#include <chrono>
// 数据处理类
class DataProcessor {
public:
DataProcessor() {
// 模拟初始化处理器,例如:分配资源
buffer = new int[1000];
}
~DataProcessor() {
// 清理资源,例如:释放资源
delete[] buffer;
}
void processData() {
// 数据处理逻辑
// 例如:模拟一些处理工作
int sum = 0;
for (int i = 0; i < 1000; ++i) {
sum += buffer[i];
}
// 模拟一些处理结果输出
result = sum;
}
private:
int* buffer;
int result;
};
// 没有对象池的批处理函数
void processBatchWithoutPool(const std::vector<int>& batch) {
for (int value : batch) {
DataProcessor processor;
processor.processData();
}
}
int main() {
std::vector<int> batch(1000000, 1);
auto start = std::chrono::high_resolution_clock::now();
processBatchWithoutPool(batch);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "Time taken without object pool: " << duration.count() << " seconds" << std::endl;
return 0;
}
在这段代码中,每次处理数据时都会创建和销毁 DataProcessor 对象,导致频繁的内存分配和释放,影响了系统性能。
3. 优化代码:引入对象池
为了克服上述瓶颈,我们将引入对象池。首先,定义一个通用的对象池类:
#include <queue>
#include <mutex>
#include <memory>
template<typename T>
class ObjectPool {
public:
std::shared_ptr<T> acquire() {
std::lock_guard<std::mutex> lock(mutex_);
if (!pool_.empty()) {
auto obj = std::move(pool_.front());
pool_.pop();
return obj;
}
return std::make_shared<T>();
}
void release(std::shared_ptr<T> obj) {
std::lock_guard<std::mutex> lock(mutex_);
pool_.push(std::move(obj));
}
private:
std::queue<std::shared_ptr<T>> pool_;
std::mutex mutex_;
};
接下来,修改processBatch函数以利用对象池:
ObjectPool<DataProcessor> processorPool;
void processBatchWithPool(const std::vector<int>& batch) {
for (int value : batch) {
auto processor = processorPool.acquire();
processor->processData();
processorPool.release(processor);
}
}
int main() {
std::vector<int> batch(1000000, 1);
auto start = std::chrono::high_resolution_clock::now();
processBatchWithPool(batch);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "Time taken with object pool: " << duration.count() << " seconds" << std::endl;
return 0;
}
4. 性能测试结果
为了量化优化效果,我们运行上述两个版本的程序,并记录解析数据所需的时间。
4.1 不使用对象池的测试结果
Time taken without object pool: 1.234 seconds
4.2 使用对象池的测试结果
Time taken with object pool: 0.456 seconds
4.3 性能优化数据对比
通过上述测试,我们可以看到:
- 不使用对象池的情况下,解析 100 万条数据所需时间约为 1.234 秒。
- 使用对象池的情况下,解析 100 万条数据所需时间约为 0.456 秒。
通过对象池的优化,时间减少了约 63%。这是因为对象池减少了频繁的内存分配和释放操作,提高了内存管理效率。

1826

被折叠的 条评论
为什么被折叠?



