一个Generic Memory Pool的剖析

本文深入分析了GenericMemoryPool的设计与实现细节,探讨了其内存管理机制,如内存对齐分配及链表管理等,并提出了若干改进建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个Generic Memory Pool的剖析

By 凝霜(Loki)

一个人的战争(http://blog.youkuaiyun.com/MDL13412)


这个项目的主页在http://sourceforge.net/projects/memorypool/,个人感觉代码写的比较好,设计的也很精巧,特此剖析。

为了尽量减少代码,我会将一些注释去掉,并用我的注释替换,另外,对于原作者的版权声明,我仅在文章开头给出,后面代码中将其去掉。

/*!
\author Matt Wash
*/

首先,这个项目的源码文件夹中包含8个文件:

ExampleClasses.h   main.cpp    Pool.h   GenericMemoryPool.sln   MattExampleClasses.h  Uncopyable.h   GenericMemoryPool.vcproj   PooledObject.h

其中Pool.h  PooledObject.h  Uncopyable.h是核心代码,ExampleClasses.h  MattExampleClasses.h    main.cpp 是测试代码,GenericMemoryPool.slnGenericMemoryPool.vcproj是Visual Studio 2003及以上版本组织项目使用的,由于我是在Linux下剖析的,使用的IDE是NetBeans,所以这两个文件对我的剖析没有任何用处,我直接将其删除,然后使用NetBeans重新组织项目。

让我们从最小 的Uncopyable.h开始剖析

#ifndef UNCOPYABLE_H
#define UNCOPYABLE_H

// 这个是类的作用是阻止一个类被拷贝(包括复制构造和赋值操作)
//
// 其原理很简单, 就是将复制构造函数和operator =声明为private,
// 由于并不真正使用, 所以只要函数声明, 不需要实现.
// 然后任何不想被拷贝的类D从Uncopyable继承, 这样如果对类D进行
// 拷贝操作就会由于Uncopyable声明的函数访问权限不足而失败.
//
// 详细资料请参考 <Effective C++>
class Uncopyable
{
protected:
  Uncopyable() {}
  ~Uncopyable() {}
  
private:
  Uncopyable(const Uncopyable&);
  Uncopyable& operator=(const Uncopyable&);
};

#endif // UNCOPYABLE_H
接下来我们剖析 Pool.h

#ifndef POOL_H
#define POOL_H

// Comment By:  凝霜
// E-mail:      mdl2009@vip.qq.com
// Blog:        http://blog.youkuaiyun.com/mdl13412

#include <new>
#include "Uncopyable.h"

// Pool中最多能存储的实例数量, 超出最大数量会导致错误,
// 在Debug版本中会对分配进行检测, 超过最大数量会触发assert
const int g_MaxNumberOfObjectsInPool = 1000;

// 首先是简述一下代码的公共接口:
// static void *Allocate ()         // 从Pool中分配一个实例
// static void 	Free (void *ptr)    // 将一个从Pool中分配的实例释放(归还Pool)
//
// static void 	Create ()           // 显式创建Pool
// static void 	Destroy ()          // 显式销毁Pool
//
// ~Pool()                          // 销毁Pool

// 程序框架设计的很小巧, 思路也很清晰, 我主要分析内存对齐分配的问题
// 另外, 这个Pool用了Singleton模式

template <class T>
class Pool : private Uncopyable
{
public:
    static void* Allocate()
    {
        // 检测Pool是否已经分配, 防止发生错误
        if (instance().memory_ == NULL) instance().createPool();

        // Debug模式下检测Pool是否还有容量
        assert(instance().numFreeBlocks_ > 0 && "the pool is empty");

        // 分配实例对象
        return instance().allocateBlock();
    }

    static void Free(void* ptr)
    {
        // 检测Pool是否分配以及待释放指针是否为空, 以防止发生错误
        if (!instance().memory_ || !ptr) return;

        // 在Debug模式下检测待释放对方是否是在Pool分配的
        assert(instance().containsPointer(ptr)
               && "object not allocated from this pool");

        // 将释放的内存还给Pool
        instance().freeBlock((FreeBlock*) ptr);
    }

    static void Create()
    {
        if (instance().memory_ == NULL) instance().createPool();
    }

    static void Destroy()
    {
        if (instance().memory_ != NULL) instance().destroyPool();
    }

    ~Pool() { destroyPool(); }

private:
    // 用来维护内存链表, 这个struct是为了提高可读性
    struct FreeBlock { FreeBlock* next; };

    Pool() : memory_(NULL), head_(NULL), blockSize_(0) { }

    static Pool& instance()
    {
        static Pool pool;
        return pool;
    }

    // 这段代码是本框架的难点, 但同时也是亮点
    void createPool()
    {
        numFreeBlocks_ = g_MaxNumberOfObjectsInPool;    // Pool容量

        // 内存块的大小要满足内存对齐的要求, 这样才能使CPU寻址最快.
        // __alignof(T)是为了检测T对齐的粒度, 因为用户可以指定对齐的粒度,
        // 所以不可以Hard Code, 在早期的STL内存配置器中, 对齐粒度固定为8.
        // 但是如果用户指定对齐为16, 那么就会出现错误, 这里的动态检测是亮点
        //
        // 举个例子, 假设sizeof(T) = 20, __alignof(T) = 16
        // 那么 blockSize = 20
        //      diff = 20 % 16 = 4
        // 因为 diff != 0, 所以
        //      blockSize += 20 + 16 - 4 = 32
        // 这样就满足内存对齐的要求了, 很不错的算法
        //
        // 这里有一个问题, 如果sizeof(T)比一个指针要小, 那么会浪费内存
        blockSize_ = sizeof (T);
        size_t diff = blockSize_ % __alignof(T);
        if (diff != 0) blockSize_ += __alignof(T) - diff;

        // 注意: 如果分配的blockSize比一个指针还小, 那么就至少要分配一个指针的大小
        if (blockSize_ < sizeof (uintptr_t)) blockSize_ = sizeof (uintptr_t);

        memory_ = alignedMalloc(g_MaxNumberOfObjectsInPool * blockSize_, __alignof(T));

        // 检测分配内存是否满足内存对齐条件, 不过个人感觉没必要进行检测
        assert(isAligned(memory_, __alignof(T)) && "memory not aligned");

        // 将FreeBlock链表头设置为分配的值
        head_ = (FreeBlock*) memory_;
        head_->next = NULL;
    }

    void destroyPool()
    {
        alignedFree(memory_);
        memory_ = NULL;
    }

    // 检测一个指针是否是在Pool中分配的, 用于防止错误释放
    bool containsPointer(void* ptr)
    {
        return (uintptr_t) ptr >= (uintptr_t) memory_ &&
            (uintptr_t) ptr < (uintptr_t) memory_ + blockSize_
            * g_MaxNumberOfObjectsInPool;
    }

    FreeBlock* allocateBlock()
    {
        // 分配block是一个O(1)的算法,链表头始终是空闲节点
        FreeBlock* block = head_;

        // 这里维护的是空闲节点的数目, 即Pool的剩余容量
        if (--numFreeBlocks_ != 0)
        {
            if (head_->next == NULL)
            {
                // If the block has not been previously allocated its next pointer
                // will be NULL so just update the list head to the next block in the pool 
                head_ = (FreeBlock*) (((uintptr_t) head_) + blockSize_);
                head_->next = NULL;
            }
            else
            {
                // The block has been previously allocated and freed so it 
                // has a valid link to the next free block
                head_ = head_->next;
            }
        }

        return block;
    }

    void freeBlock(FreeBlock* block)
    {
        // 将内存归还到链表头
        if (numFreeBlocks_ > 0) block->next = head_;
        head_ = block;

        // 维护空闲节点数目
        numFreeBlocks_++;
    }

    void* alignedMalloc(size_t size, int alignment)
    {
        // 分配足够的内存, 这里的算法很经典, 早期的STL中使用的就是这个算法

        // 首先是维护FreeBlock指针占用的内存大小
        const int pointerSize = sizeof (void*);

        // alignment - 1 + pointerSize这个是FreeBlock内存对齐需要的内存大小
        // 前面的例子sizeof(T) = 20, __alignof(T) = 16,
        // g_MaxNumberOfObjectsInPool = 1000
        // 那么调用本函数就是alignedMalloc(1000 * 20, 16)
        // 那么alignment - 1 + pointSize = 19
        const int requestedSize = size + alignment - 1 + pointerSize;

        // 分配的实际大小就是20000 + 19 = 20019
        void* raw = malloc(requestedSize);

        // 这里实Pool真正为对象实例分配的内存地址
        uintptr_t start = (uintptr_t) raw + pointerSize;
        // 这个算法的剖析见我剖析的STL源码的<stl_alloc.h>第355行
        // http://blog.youkuaiyun.com/mdl13412/article/details/6638405
        void* aligned = (void*) ((start + alignment - 1) & ~(alignment - 1));

        // 这里维护一个指向malloc()真正分配的内存
        *(void**) ((uintptr_t) aligned - pointerSize) = raw;

        // 返回实例对象真正的地址
        return aligned;
    }

    // 这里是内部维护的内存情况
    //                   这里满足内存对齐要求
    //                             |
    // ----------------------------------------------------------------------
    // | 内存对齐填充 | 维护的指针 | 对象1 | 对象2 | 对象3 | ...... | 对象n |
    // ----------------------------------------------------------------------
    // ^                     | 指向malloc()分配的地址起点
    // |                     |
    // -----------------------
    void alignedFree(void* aligned)
    {
        // 释放操作很简单了, 参见上图
        void* raw = *(void**) ((uintptr_t) aligned - sizeof (void*));
        free(raw);
    }

    bool isAligned(void* data, int alignment)
    {
        // 又是一个经典算法, 参见<Hacker's Delight>
        return ((uintptr_t) data & (alignment - 1)) == 0;
    }

private:
    void* memory_; // A pointer to the memory allocated for the entire pool              
    FreeBlock* head_; // A pointer to the head of the linked list of free blocks
    size_t numFreeBlocks_; // The current number of free blocks in the pool
    size_t blockSize_; // The size in bytes of a block in the pool (this is only
    // stored so it can be used by containsPointer() to 
    // validate deallocation and so could be omitted in a release build)
};

// 显式创建Pool
#define CREATE_POOL(T) \
  Pool<T>::Create();

// 显式销毁Pool
#define DESTROY_POOL(T) \
  Pool<T>::Destroy();

// 分配用户自定义类型, __VA_ARGS__是可变宏参数, 用于class的初始化
#define POOL_NEW(T, ...) \
  new(Pool<T>::Allocate()) T(__VA_ARGS__) \

// 销毁用户自定义类型
#define POOL_DELETE(T, ptr) \
  ptr->~T();                \
  Pool<T>::Free(ptr); 

// 用于POD类型, 一切为了效率
#define POOL_ALLOC(T) \
  (T*)Pool<T>::Allocate();

#define POOL_FREE(T, ptr) \
  Pool<T>::Free(ptr);

#endif // POOL_H
总结:

可以改进的地方:

1) Pool的容量应该由用户自定义,不应该使用全局变量,这样自由度很低

2) 对于Pool操作宏,可以使用Traits技术根据POD和用户自定义类型进行编译期派发,而不用维护两套不同的宏,这样可以实现对程序员透明

3) 内存对齐检查这个过程个人认为只有在写本框架的时候才有用,开发完成后应该去掉

4) Uncopyable这个类其实可以去掉, 因为其它地方都没有用到,用户也不会去复用,那么直接将Pool复制构造函数和operator =声明为private即可。

5) 这个框架不是线程安全的,至少应该在创建Pool的整个过程中lock,否则会出现问题。

6) Pool没有容量的时候不能正确工作,应该增加一个二级分配器,应对这种情况,效率可以适当放要求

7) 对于sizeof小于指针的对象,会浪费内存,应该进行特化,提供高性能的版本

做的不错的地方:

1) 对FreeBlock的定义,提高了程序可读性

2) 内存对齐分配问题,速度快,而且能减少内存碎片的产生

3) 内存对齐算法使用了__alignof进行检测,没有Hard Code,避免了Hard Code引发的问题

4) 接口设计的很好,Create()接口给程序员自己控制Pool建立的时机,非常好 

5) 获取对象实例的时间复杂度为O(1),很好

6) Singleton模式运用的很好

7) 文档写的很好,讲解的也不错:-)

static int rproc_host_probe(struct platform_device *pdev) { struct resource *mem; struct device *dev = &pdev->dev; struct rproc_host_priv *priv; int ret, virq; struct rproc *rproc; void *__iomem reg_base; void *shm_base, *gen_pool_base; size_t shm_size, gen_pool_size; uint64_t shm_phys, gen_pool_phys; int irq_count, remote_irq_count; #if defined(CONFIG_ZONE_DEVICE) struct dev_pagemap *pgmap; #endif rproc = devm_rproc_alloc(dev, "nbl_rproc_remote", &nebula_rproc_ops, NULL, sizeof(*priv)); if (!rproc) return -ENOMEM; priv = rproc->priv; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { ret = -EINVAL; goto err_free_priv; } reg_base = devm_ioremap_resource(dev, mem); if (IS_ERR(reg_base)) { ret = PTR_ERR(reg_base); goto err_free_priv; } mem = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!mem) { ret = -EINVAL; goto err_unmap_reg; } shm_phys = mem->start; shm_size = resource_size(mem); #if defined(CONFIG_ZONE_DEVICE) /* * It's important not using MEMORY_DEVICE_GENERIC here. For device * type other than MEMORY_DEVICE_PRIVATE and MEMORY_DEVICE_COHERENT, * the page refcount will be reset to 1 after it has been freed (See * free_zone_device_page). We have a race here since memory allocation * can happen in parallel. Before page refcount reset to 1, the freed * page may have been allocated again and referenced by other user(s). * This will cause doulbe-free issue. */ pgmap = &priv->pgmap; pgmap->type = MEMORY_DEVICE_COHERENT; pgmap->range.start = shm_phys; pgmap->range.end = shm_phys + shm_size - 1; pgmap->nr_range = 1; pgmap->ops = &pagemap_ops; pgmap->owner = dev; shm_base = devm_memremap_pages(dev, pgmap); #else shm_base = devm_memremap(dev, shm_phys, shm_size, MEMREMAP_WB); #endif if (!shm_base) { ret = -EINVAL; goto err_unmap_reg; } dev_info(dev, "shared memory mapped @ %px, size %lx\n", shm_base, shm_size); priv->magic = NBL_RPROC_MAGIC; priv->reg_base = reg_base; priv->shm_phys = shm_phys; priv->shm_base = shm_base; priv->shm_size = shm_size; priv->rproc = rproc; INIT_WORK(&priv->attach_work, rproc_host_attach_work); priv->workq = alloc_workqueue("nebula_rproc_host", WQ_MEM_RECLAIM, 0); if (!priv->workq) { ret = -ENOMEM; goto err_unmap_reg; } #if defined(CONFIG_ZONE_DEVICE) priv->dma_buf_pool = gen_pool_create(PAGE_SHIFT, -1); #else priv->dma_buf_pool = gen_pool_create(0, -1); #endif if (priv->dma_buf_pool == NULL) { dev_err(dev, "failed to create gen pool\n"); ret = -ENOMEM; goto err_unmap_shm; } gen_pool_phys = shm_phys + RSC_TAB_SIZE; gen_pool_base = shm_base + RSC_TAB_SIZE; gen_pool_size = shm_size - RSC_TAB_SIZE; memset(gen_pool_base, 0, gen_pool_size); ret = gen_pool_add_virt(priv->dma_buf_pool, (unsigned long)gen_pool_base, gen_pool_phys, gen_pool_size, -1); if (ret) { dev_err(dev, "failed to gen_pool_add_virt, ret=%d\n", ret); goto err_release_gen_pool; } set_dma_ops(dev, &rproc_dma_ops); rproc->auto_boot = false; rproc->state = RPROC_DETACHED; ret = devm_rproc_add(dev, rproc); if (ret) { dev_err(dev, "rproc_add failed\n"); goto err_release_gen_pool; } irq_count = platform_irq_count(pdev); ret = of_property_read_u32(dev->of_node, "remote_irq_count", &remote_irq_count); BUG_ON(ret != 0); if (remote_irq_count) { dev_info(dev, "notify using physical interrupt\n"); priv->notify_with_phys_irq = true; BUG_ON(remote_irq_count != irq_count - 1); } virq = platform_get_irq(pdev, 0); ret = devm_request_irq(&pdev->dev, virq, rproc_ctrl_irq_handler, IRQF_SHARED, dev_name(&pdev->dev), priv); BUG_ON(ret < 0); INIT_LIST_HEAD(&priv->node); priv->dev = dev; priv->num_queues = irq_count - 1; priv->irq_info = kzalloc(irq_count * sizeof(struct irq_info), GFP_KERNEL); if (!priv->irq_info) goto err_remove_rproc; priv->tasks = kzalloc(priv->num_queues * sizeof(struct task_struct*), GFP_KERNEL); if (!priv->tasks) goto err_remove_irq_info; platform_set_drvdata(pdev, priv); ret = sysfs_create_group(&dev->kobj, &rproc_dev_group); WARN_ON(ret != 0); mutex_lock(&rproc_devices_lock); list_add(&priv->node, &rproc_devices); mutex_unlock(&rproc_devices_lock); queue_work(priv->workq, &priv->attach_work); return 0; err_remove_irq_info: kfree(priv->irq_info); err_remove_rproc: rproc_del(rproc); err_release_gen_pool: gen_pool_destroy(priv->dma_buf_pool); err_unmap_shm: devm_memunmap(dev, shm_base); err_unmap_reg: devm_iounmap(dev, reg_base); err_free_priv: rproc_free(rproc); return ret; }
07-31
[root@master logging]# helm install --namespace minio minio-operator minio/operator \ > --set console.resources.requests.memory=128Mi \ > --set console.resources.limits.memory=256Mi \ > --set operator.resources.requests.memory=64Mi \ > --set operator.resources.limits.memory=128Mi NAME: minio-operator LAST DEPLOYED: Fri Jun 13 12:32:32 2025 NAMESPACE: minio STATUS: deployed REVISION: 1 TEST SUITE: None [root@master logging]# kubectl get crd | grep min.io policybindings.sts.min.io 2025-06-13T04:32:33Z tenants.minio.min.io 2025-06-13T04:32:33Z [root@master logging]# kubectl -n minio create secret generic minio-creds \ > --from-literal=rootUser=minioadmin \ > --from-literal=rootPassword=minioadmin error: failed to create secret secrets "minio-creds" already exists [root@master logging]# vim minio-tenant.yaml [root@master logging]# vim minio-tenant.yaml [root@master logging]# kubectl apply -f minio-tenant.yaml Error from server (BadRequest): error when creating "minio-tenant.yaml": Tenant in version "v2" cannot be handled as a Tenant: strict decoding error: unknown field "spec.console", unknown field "spec.secret", unknown field "spec.serviceMetadata.annotations", unknown field "spec.serviceMetadata.labels" [root@master logging]# cat minio-tenant.yaml apiVersion: minio.min.io/v2 kind: Tenant metadata: name: minio-logging namespace: minio spec: image: quay.io/minio/minio:RELEASE.2024-12-18T13-15-44Z pools: - name: pool-0 servers: 1 volumesPerServer: 1 volumeClaimTemplate: metadata: name: data spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: openebs-hostpath # 移动到volumeClaimTemplate下 resources: requests: memory: 256Mi secret: name: minio-creds # 从credentials改为secret mountPath: /export subPath: "" serviceMetadata: # 替换service字段 annotations: {} labels: {} console: enabled: true replicas: 1 image: quay.io/minio/console:v0.26.0 imagePullPolicy: IfNotPresent resources: requests: memory: 128Mi limits: memory: 256Mi
06-14
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值