【C++学习】C++实现高效内存池


1.内存池简介

内存池是池化技术中的一种形式,通常我们在编写程序的时候使用new和delete关键字向操作系统申请内存。但是每一个申请内存和释放内存的时候,都需要和操作系统的系统调用进行交互,并在堆中进行内存分配。如果操作过于频繁,就会出现大量的内存碎片进而降低内存的分配性能。从而导致内存分配失败的情况。对于内存申请过程而言,其实就是一次申请指针的过程,对于每一次的内存分配,都会消耗一次分配内存的时间,如果在程序开始的时候就分配好一块合理的内存区域,当我们下次使用的时候,就可以直接从分配的内存中进行使用,降低申请内存分配时操作系统进行复杂调度过程的时间。

优势:
1.从原理上比newmalloc
2.分配内存的时候overhead
3.基本不会出现内存碎片
4.无需一个一个释放内存,只需要释放内存池就可以

2.函数实现

2.1 主函数

#include <iostream>
#include <ctime>
#include <vector>
#include <cassert>
#include "StackAlloc.h"
#include "MemoryPool.h"

using namespace std;

const int REPS = 100;
const int ELEMS = 1e8;

int main() {
    clock_t start;

    // Use standard allocator
    StackAlloc<int, std::allocator<int> > stackDefault;
    start = clock();
    for(int reps=0; reps < REPS; ++reps) {
        assert(stackDefault.empty());

        for(int elems=0; elems < ELEMS; ++elems) {
            stackDefault.push(elems);
        }

        for(int elems=0; elems < ELEMS; ++elems) {
            stackDefault.pop();
        }
    }
    std::cout << "Default Allocator Time: ";
    std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";


    // Use memory pool
    StackAlloc<int, MemoryPool<int > > stackPool;
    start = clock();
    for(int reps=0; reps < REPS; ++reps) {
        assert(stackPool.empty());

        for(int elems=0; elems < ELEMS; ++elems) {
            stackPool.push(elems);
        }
        for(int elems=0; elems < ELEMS; ++elems) {
            stackPool.pop();
        }
    }
    std::cout << "Memory Allocator Time: ";
    std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n";

    return 0;
}

多次执行链式栈的入栈出栈过程,对时间性能进行比较。分别使用标准库中的allocator和自己实现的内存池进行内存测试。对于newdelete,在变量分配内存给空间的同时对变量进行初始化,std::allocator将变量初始化和内存分配隔离开。

2.2 StackAlloc.h

#ifndef OPTIMIZED_STACKALLOC_H
#define OPTIMIZED_STACKALLOC_H
#include <memory>

template <typename T>
struct StackNode_{
    T data;
    StackNode_* prev;
};

template <typename T, typename Alloc=std::allocator<T > >
class StackAlloc {
public:

    typedef StackNode_<T> node;
    typedef typename Alloc::template rebind<node>::other allocator;

    StackAlloc();

    ~StackAlloc();

    bool empty();

    void clear();

    void push(T element);

    void pop();

    void top();

private:

    allocator allocator_;
    Node* head_;

};
#endif //OPTIMIZED_STACKALLOC_H

1.结构体为数据值和指向上一个结点的指针。
2.template <typename T, typename Alloc=std::allocator<T > >定义一个模板类,默认内存分配器是std::allocator
3.typedef typename Alloc::template rebind<node>::other allocator;
(1)typedef ... allocator给内存分配器重新设置一个别名。
(2)typename关键字是进行类别限定, 对于后面的部分在标准库中可以被认为是静态数据成员、静态成员函数以及嵌套类型。使用该关键字说明后面的是一个类型而不是一个成员变量。
(3)rebind如果已经为类型T创建了一个内存分配器allocator<T>,如果需要按照相同的策略对另外一个类型U构建一个分配器。那么可以使用allocator<U> = allocator<T>::rebind<U>::other,这样如果是我们自己定义的分配器模板allocator,就可以使用该方式,快速地实现多类型的构建过程(同族)。因为在构建T的分配器的的时候,是一个模板方式进行定义的,且rebind依赖于allocator,所以也要声明为模板。

2.3 StackAlloc.cpp

2.4 禁用拷贝赋值

为降低程序代价,禁用拷贝赋值,仅使用移动赋值,因为内存池拷贝的代价巨大。

MemoryPool& operator=(const MemoryPool& mp) = delete;
MemoryPool& operator=(const MemoryPool& mp) noexcept;

C++11中的delect表示对前面的函数声明进行禁用。

2.5 静态断言与断言

断言方式:assert是在运行时进行断言,而static_assert是在编译阶段进行断言。

2.6 使用reinterpret_cast

在对内存池进行删除的时候,通过对delete进行重载,并使用reinterpret_cast将指针强制转换为void *类型。

猜你喜欢

转载自blog.csdn.net/twt520ly/article/details/80924224