内存池及其C++代码实现

引言

内存池通过预分配和高效管理内存,显著提高了内存分配的性能和效率,非常适合需要频繁分配和释放内存的应用场景,也是大型项目中较为常见的组件。

我们为什么要使用内存池?

当一个程序运行时间较长时,频繁的 new / delete 具有不确定性,操作系统管理的连续内存被分为很多的非连续碎片,内存碎片大量的产生。这时,我们想要得到一块较为大的连续内存,就会有些困难,久而久之,程序就会因为无法满足大内存的需求崩溃。

我们使用内存池就是为了避免这种情况的发生,我们提前分配一块较大的内存,自己进行管理,让我们的程序能够长久的运行下去。

实现原理

内存池的实现难点在于:

  • 如何划分内存
  • 记录已使用和未使用的内存
  • 搜寻合适大小的内存

关于这些问题,有许多专业的论文提出了许多种算法。但是我在这里不会讲,我讲一讲我是如何实现一个简单的高效率内存池的。

划分内存

首先我们要明白我们的需求:为程序稳定的分配大内存。

所以不考虑小内存的分配,因为操作系统做的已经足够好,这样做没有意义。

先定义一个 block_size,即能得到内存的最小单位。

我们能从中取到的内存只可能是 block_size << n (n 为非负整数)。

这样做的原因是是为了记录已使用和未使用的内存。

记录已使用和未使用的内存

我们还需要一个固定大小的数组(如果总大小为 m, 数组大小为 (m / block_size) * 2)。

初始状态:

我们把一个二叉树压缩到数组中。

树的叶节点为 block_size。

父节点的值是子节点的二倍。

根节点为总大小。

重要规则:

每一个节点记录着该节点下能得到的最大内存

分配时:

已经使用的内存将它节点(刚好大于等于你需要的内存大小的第一个查找到的节点)的值置为0,同时递归的向上修改父节点的值。

父节点值根据子节点而定:

  • 如果子节点的值相同且该值等于对应层数的大小(例如根节点是总大小,根节点下的子节点就是总大小的一半,以此类推……),父节点就可以置为父节点对应层数的大小(也就是此时子节点值的2倍)。
  • 否则父节点的值等于子节点值中较大的那个值。

归还时:

我们归还内存时也根据这些规则对数组进行修改:

根据大小和位置的偏移量锁定其对应置0的那个节点。

把它的 size 加回来,递归的按规则修改父节点。

搜寻合适大小的内存

比较简单,根据节点的大小就能确定能否分配,就这样选择子节点,直到内存大小刚刚合适。

注意选择时有偏好,可以尽量保留连续的大内存:

  • 如果子节点都能满足要求时选更小的的那一个。
  • 子节点值相同时选左边的那一个。

代码实现

以下代码来自于个人项目

Mutex 对应 std::mutex  Lock 对应 std::lock_guard 可以自行替换(别忘了 uint64 NoCopy,懂得都懂) 

BufferPool.hpp

//
// Created by taganyer on 24-7-24.
//

#ifndef BASE_BUFFERPOOL_HPP
#define BASE_BUFFERPOOL_HPP

#ifdef BASE_BUFFERPOOL_HPP

#include <vector>
#include "Base/Mutex.hpp"

namespace Base {

    class BufferPool : NoCopy {
    public:
        constexpr static uint64 block_size = 1 << 12;

        /// 返回与 block_size 向上对齐的大小。
        static uint64 round_size(uint64 target);

        class Buffer;

        explicit BufferPool(uint64 total_size);

        ~BufferPool();

        Buffer get(uint64 size);

        [[nodiscard]] uint64 total_size() const { return _size; };

        [[nodiscard]] uint64 max_block() const { return _rest[0]; };

    private:
        uint64 _size = 0;

        char* _buffer;

        Mutex _mutex;

        std::vector<uint64> _rest;

        void put(Buffer &buffer);

        [[nodiscard]] std::pair<uint64, char *> positioning(uint64 size) const;

        [[nodiscard]] uint64 location(const char* target, uint64 size) const;

        friend class Buffer;

    public:
        /// 自动归还内存
        class Buffer : NoCopy {
        public:
            Buffer(Buffer &&other) noexcept : _buf(other._buf), _size(other._size), _pool(other._pool) {
                other._buf = nullptr;
                other._size = 0;
            };

            ~Buffer() { put_back(); };

            char* data() { return _buf; };

            void put_back() { if (_buf) _pool.put(*this); };

            [[nodiscard]] const char* data() const { return _buf; };

            [[nodiscard]] uint64 size() const { return _size; };

            [[nodiscard]] operator bool() const { return _buf; };

        private:
            char* _buf;
            uint64 _size;
            BufferPool &_pool;

            Buffer(char* b, uint64 s, BufferPool &pool) : _buf(b), _size(s), _pool(pool) {};

            friend class BufferPool;

        };

    };

}

#endif

#endif //BASE_BUFFERPOOL_HPP

BufferPool.cpp

//
// Created by taganyer on 24-7-24.
//

#include "BufferPool.hpp"

using namespace Base;

static inline std::pair<uint64, uint64> parent_sibling(uint64 i) {
    uint64 p, s;
    if (i & 1) {
        p = (i - 1) >> 1;
        s = i + 1;
    } else {
        p = (i - 2) >> 1;
        s = i - 1;
    }
    return { p, s };
}

uint64 BufferPool::round_size(uint64 target) {
    uint64 pre = 0, size = block_size;
    while (size < target && size > pre) {
        pre = size;
        size <<= 1;
    }
    return size < pre ? pre : size;
}

BufferPool::BufferPool(uint64 total_size) :
    _size(round_size(total_size)), _buffer(new char[_size]),
    _rest(std::vector<uint64>(_size / block_size << 1)) {
    for (uint64 s = _size, t = 1, i = 0; s >= block_size; s >>= 1, t <<= 1)
        for (uint64 end = i + t; i < end; ++i)
            _rest[i] = s;
}

BufferPool::~BufferPool() {
    assert(_rest[0] != _size ? nullptr : "memory leak");
    delete[] _buffer;
}

BufferPool::Buffer BufferPool::get(uint64 size) {
    Lock l(_mutex);
    if ((size = round_size(size)) > _rest[0])
        return { nullptr, 0, *this };
    auto [i, ptr] = positioning(size);

    assert(_rest[i] == size);
    _rest[i] = 0;
    while (i > 0) {
        auto [p, s] = parent_sibling(i);
        uint64 ms = std::max(_rest[i], _rest[s]);
        assert(_rest[p] >= ms);
        if (_rest[p] == ms) break;
        _rest[p] = ms;
        i = p;
    }
    return { ptr, size, *this };
}

void BufferPool::put(Buffer &buffer) {
    Lock l(_mutex);
    uint64 i = location(buffer._buf, buffer._size), fs = buffer._size;
    buffer._buf = nullptr;
    buffer._size = 0;

    assert(_rest[i] == 0);
    _rest[i] = fs;
    while (i > 0) {
        auto [p, s] = parent_sibling(i);
        uint64 ms = std::max(_rest[i], _rest[s]);
        if (_rest[i] == fs && fs == _rest[s]) ms = fs << 1;
        assert(_rest[p] <= ms);
        if (_rest[p] == ms) break;
        _rest[p] = ms;
        i = p;
        fs <<= 1;
    }
}

std::pair<uint64, char *> BufferPool::positioning(uint64 size) const {
    uint64 i = 0;
    int n = 0;
    for (uint64 s = _size; s != size; s >>= 1, ++n) {
        uint64 _l = (i << 1) + 1, _r = (i << 1) + 2;
        assert(_rest[_l] >= size || _rest[_r] >= size);
        i = _rest[_r] < size || _rest[_l] >= size && _rest[_l] <= _rest[_r] ? _l : _r;
    }
    return { i, _buffer + (i - ((1 << n) - 1)) * size };
}

uint64 BufferPool::location(const char* target, uint64 size) const {
    uint64 i = 0;
    for (auto s = size; _size > s; s <<= 1) ++i;
    i = (1 << i) - 1;
    return i + (target - _buffer) / size;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值