C++编程:对象池(objectpool)的简单应用

0. 引言

最近在使用 perf 进行性能分析时,发现串口数据业务代码解析占用大量CPU资源。具体是 DataRecv 函数读取串口数据并通过 ProtocolParse 类解析帧,其中频繁的内存分配与释放是一个瓶颈:每次解析新帧时都会动态分配和释放新的对象。
而应用对象池(Object Pool)通过预分配和复用对象,可减少内存操作开销,提升系统响应速度并减少内存碎片。
本文将简单介绍对象池的使用。

关于如何使用 perf 进行性能分析,i请查看我的另一篇文章:使用perf(火焰图)查看热点函数和系统调用最大延迟函数

1. 实施对象池的目的

对象池设计模式的核心思想是在程序启动时预先创建一组对象,并将其存储在一个池中。当应用程序需要新对象时,它会从池中取出一个已存在的对象,而不是创建一个新的。使用完毕后,对象被放回池中,可供后续使用。这一过程避免了昂贵的构造和析构操作,同时减少了内存碎片问题。

具体目的是:

  1. 减少内存分配与释放开销:频繁的内存操作是性能瓶颈之一,对象池通过预分配减少了这种开销。
  2. 提高系统响应速度:通过减少垃圾收集和内存管理的负担,对象池提升了系统性能。
  3. 避免内存碎片:对象池通过统一管理对象的生命周期,有效防止了内存碎片的产生。

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%。这是因为对象池减少了频繁的内存分配和释放操作,提高了内存管理效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值