STL - 内存分配 - 内存池
STL - 内存分配 - 内存池
前言
本文为:STL源码分析第二章《allocator》的阅读记录。
如果具有很多小额区块的内存分配,那么采用直接分配的方式,不仅会产生很多的碎片内存,对这些空间进行管理也会带来额外的负担。
文中记录的方法是,如果区块够大,超过128bytes,就向系统直接申请对应的内存空间;当区块小于128bytes的时候,采用内存池进行管理,一次性申请大块的内存空间,用freelist管理剩余空间。为了方便管理,当申请小区块的时候,为将申请的空间上调至8的倍数,16个freelists,管理了大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小区块。
完整代码及流程示意图
#include <iostream>
#include <stdlib.h>
#include <memory.h>
namespace {
    
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
template <bool threads, int inst/*not used*/>
class __default_alloc_template {
private:
    // 将bytes上调至8的倍数
    static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
    }
    union obj {
        union obj * free_list_link;
        char client_data[1];
    };
    static obj * volatile free_list[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN - 1) / __ALIGN - 1);
    }
    // 当发现list中没有足够的空间的时候,通过refill,
    // 传回一个大小为n的物件,并可能加入大小为n的其他区块到free list
    // 其中n已经上调至8的倍数
    static void* refill(size_t n)
    {
        int nobjs = 20;
        char * chunk = chunk_alloc(n, nobjs);
        obj * volatile * my_free_list;
        obj * result;
        obj * current_obj, * next_obj;
        if (1 == nobjs) return (chunk);
        
        my_free_list = free_list + FREELIST_INDEX(n);
        result = (obj*)chunk;
        *my_free_list = next_obj = (obj*)(chunk + n);
        for (int i = 1; ; i++) {
            current_obj = next_obj;
            next_obj = (obj*)((char*)next_obj + n);
            if (nobjs - 1 == i) {
                current_obj->free_list_link = 0;
                break;
            } else {
                current_obj->free_list_link = next_obj;
            }
        }
        return (result);
    }
    // 配置大块空间,可容纳nobjs个大小为“size”的区块
    // 如果,无法配置nobjs个区块,nobjs的值会减小
    static char* chunk_alloc(size_t size, int &nobjs)
    {
        char * result;
        size_t total_bytes = size * nobjs;
        size_t bytes_left = end_free - start_free;
        if (bytes_left >= total_bytes) {
            // 内存池中剩余空间完全满足需求量
            result = start_free;
            start_free += total_bytes;
            return result;
        } else if (bytes_left >= size) {
            // 内存池剩余空间不能完全满足需求量,但足够供应一个以上区块
            nobjs = bytes_left / size;
            total_bytes = size * nobjs;
            result = start_free;
            start_free += total_bytes;
            return result;
        } else {
            // 内存池剩余空间连一个区块大小都无法提供
            size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
            if (bytes_left > 0) {
                // 内存池汇中还有剩余,先配给适当的freelist
                obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
                // 调整freelist,将内存池剩余空间编入
                ((obj*)start_free)->free_list_link = *my_free_list;
                *my_free_list = ((obj*)start_free);
            }
            
            // 配置heap空间
            start_free = (char*)malloc(bytes_to_get);
            if (0 == start_free) {
                // heap 空间不足,malloc失败
                int i;
                obj * volatile * my_free_list, *p;
                // 尝试从freelist中查找是不是有足够大的没有用过的区块
                for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
                    my_free_list = free_list + FREELIST_INDEX(i);
                    p = *my_free_list;
                    if (0 != p) {
                        *my_free_list = p->free_list_link;
                        start_free = (char*)p;
                        end_free = start_free + i;
                        return (chunk_alloc(size, nobjs));
                    }
                }
                end_free = 0;
                start_free = (char*)malloc(bytes_to_get); // 再次尝试,此处原本为malloc_alloc::allocate,内部实现,如果失败后会调用回调,然后再反复尝试
                if (0 == start_free)
                {
                    printf("out of memory\n");
                    exit(1);
                }
            }
            heap_size += bytes_to_get;
            end_free = start_free + bytes_to_get;
            // 调用自己,回了修正nobjs
            return (chunk_alloc(size, nobjs));
        }
    }
    // chunk allocation state
    static char *start_free; // 内存池的起始位置,只在chunk_alloc中变化
    static char *end_free;   // 内存池的结束位置,只在chunk_alloc中变化
    static size_t heap_size;
public:
    static void * allocate(size_t n) {
        obj * volatile * my_free_list;
        obj * result;
        // 大于最大值的时候直接分配内存
        if (n > (size_t) __MAX_BYTES)
            return malloc(n); // 此处不用,内存不足时,反复尝试的机制
        my_free_list = free_list + FREELIST_INDEX(n);
        result = *my_free_list;
        if (result == nullptr) {
            // 没有找到可用的freelist,准备重新填充freelist
            void *r = refill(ROUND_UP(n));
            return r;
        }
        *my_free_list = result->free_list_link;
        return (result);
    }
    static void deallocate(void *p, size_t n)
    {
        obj *q = (obj *)p;
        obj * volatile * my_free_list;
        // 大于最大值,直接释放内存
        if (n > (size_t) __MAX_BYTES) {
            free(p);
            return;
        }
        my_free_list = free_list + FREELIST_INDEX(n);
        q->free_list_link = *my_free_list;
        *my_free_list = q;
    }
};
template <bool threads, int inst>
typename __default_alloc_template<threads, inst>::obj * volatile 
    __default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {0};
template<bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
}
using My_Alloc = __default_alloc_template<false, 0>;
int main()
{
    int nCount = 4;
    int *a = (int *)My_Alloc::allocate(sizeof(int)*nCount);
    for (int i=0; i<nCount; ++i)
    {
        a[i] = i;
    }
    std::cout << "a[](" << a << "): ";
    for (int i=0; i<nCount; ++i)
    {
        std::cout << a[i] << " ";
    }
    My_Alloc::deallocate(a, sizeof(int)*nCount);
    std::cout << std::endl;
    std::cout << "a[](" << a << "): ";
    for (int i=0; i<nCount; ++i)
    {
        std::cout << a[i] << " ";
    }
    return 0;
}
输出为:
a[](00175A20): 0 1 2 3
a[](00175A20): 1530416 1 2 3
其中static void * allocate(size_t n) 对应的示意图如下:

其中static void deallocate(void *p, size_t n)对应的示意图如下:

小结
整个过程对应的示意图如下:

整体流程大致说明:
- 一开始有个freelist列表,系统的内存空间;
- 请求分配内存;
- 如果分配的内存块很大,直接从系统内存中申请对应大小的内存空间返回;
- 如果分配的内存块比较小,从freelist中查找,
- 如果没找到,那么从memory pool中查找特定大小的内存空间,
- 如果还没找到,那么从系统内存中申请特定大小的内存空间,一部分返回放到memory pool,一部分返回放到freelist,还有一部分返回给内存申请者。
接下来就可以进一步理解《learn nginx - 共享内存 - 小记》。
    版权说明
 
作者: grassofsky
出处: http://www.cnblogs.com/grass-and-moon
本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(grass-of-sky@163.com)咨询.
 
                    
                     
                    
                 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号