umemory教程 () 1.为什么需要使用内存池?

在C/C 我们通常使用它malloc,free或new,delete动态分配内存。

一方面,由于这些函数涉及系统调用,频繁调用必然导致程序性能损失;

另一方面,小块内存的频繁分配和释放会导致大量内存碎片的产生。当碎片积累到一定量时,不能分配到连续的内存空间。系统必须对碎片进行分类,以满足连续空间的分配,这不仅会导致系统性能损失,而且程序对内存的利用率会很低。

当然,如果我们的程序不需要经常分配和释放小块内存,就不需要直接使用内存池malloc,free或new,delete函数即可。

当然,如果我们的程序不需要经常分配和释放小块内存,就不需要直接使用内存池malloc,free或new,delete函数即可。

二、内存池实现方案

内存池的实现原理大致如下:

提前申请由内存池管理的大内存,并将其分为小供应程序。程序使用后,将内存返回到内存池(没有真正从系统中释放)。当程序再次从内存池中要求内存时,内存池将可用的内存片返回到程序中。

在设计内存池实现方案时,需要考虑以下问题:

内存池能自动生长吗?

如果内存池的最大空间是固定的(即非自动增长),那么当内存池中的内存被要求时,程序就不能再从内存池要求到内存。因此,需要根据程序对内存的实际使用情况来确定是否需要自动生长。

内存池的总内存占用是否只增不减?

若内存池自动增长,则涉及内存池总内存占用是否只增不减的问题。想象一下,程序要求1000个大小为1000的自动增长内存池KB内存片,使用后全部归还给内存池,假设程序后的逻辑最多,请求10个100KB内存片,那么内存池中900个100KB内存片一直闲置,程序内存占用永远不会下降。需要考虑内存占用大小的程序。

内存池中内存片的大小是否固定?

如果从内存池中要求的内存片的尺寸不固定,则内存池中每个可用内存片的尺寸不一致。当程序再次要求内存片时,内存池需要测量匹配最佳尺寸的内存片和匹配操作时间。虽然最佳尺寸的内存片可以减少内存的浪费,但可能会导致匹配时间的延长。

线程安全吗?

内存片是否允许在多线程中同时从同一内存池中要求和归还?内存池或用户可以保证该线程的安全性。

内存片分配和归还到内存池后,内容需要清除吗?

在将内存片归还到内存池后,程序可能会出现的地址指针进行内存读写操作,这将导致不可预测的结果。内容清零只能尽可能多地抛出问题,但不能解决任何问题,内容清零会消耗一定的CPU时间。因此,最好由内存池的用户来确保这种安全。

是否兼容std::allocator?

STL大多数标准库支持用户提供自定义的内存分配器,默认使用std::allocator,如std::string:

typedef basic_string<char, char_traits<char>, allocator<char> > string;

如果我们的内存池与我们的内存池兼容std::allocator,然后我们可以用自己的内存池代替默认情况std::allocator如:

typedef basic_string<char, char_traits<char>, MemoryPoll<char> > mystring;更多Linux内核视频教程资料文档私信【

内核大礼包】自行获取。

三、内存池的具体实现

计划实现内存池管理的类别MemoryPool,它具有以下特点:

内存池长内存池的总尺寸。

三、内存池的具体实现

计划实现内存池管理的类别MemoryPool,它具有以下特点:

内存池的总尺寸自动增加。内存片的大小固定在内存池中。支持线程安全。内存片归还后,清除内容。兼容std::allocator。由于内存池内存片的大小是固定的,不涉及需要匹配最合适尺寸的内存片。由于插入和删除操作频繁,但搜索较少,内存池中的内存片采用链表数据结构进行管理。

MemoryPool有两个链表,都是双向链表(设计成双向链表主要是为了在去除指定元素时快速定位元素的前后元素,从而在去除元素后连接其前后元素,保证链表的完整性):

data_element_ 记录和分配的内存片。free_element_ 记录未分配的内存片。

MemoryPool实现代码使用代码std::mutex等C 11支持的特点,因此,编译器需要最低支持C 11:#ifndef PPX_BASE_MEMORY_POOL_H_#define PPX_BASE_MEMORY_POOL_H_#include <climits>#include <cstddef>#include <mutex>namespace ppx{ namespace base{ template <typename T, size_t BlockSize = 4096, bool ZeroOnDeallocate = true> class MemoryPool{ public: /* Member types */ typedef T value_type; typedef T* pointer; typedef T& reference; typedef const T* const_pointer; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef std::false_type propagate_on_container_copy_assignment; typedef std::true_type propagate_on_container_move_assignment; typedef std::true_type propagate_on_container_swap; template <typename U> struct rebind{ typedef MemoryPool<U> other; }; /* Member functions */ MemoryPool() noexcept; MemoryPool(const MemoryPool& memoryPool) noexcept; MemoryPool(MemoryPool&& memoryPool) noexcept; template <class U> MemoryPool(const MemoryPool<U>& memoryPool) noexcept; ~MemoryPool() noexcept; MemoryPool& operator=(const MemoryPool& memoryPool) = delete; MemoryPool& operator=(MemoryPool&& memoryPool) noexcept; pointer address(reference x) const noexcept; const_pointer address(const_reference x) const noexcept; // Can only allocate one object at a time. n and hint are ignored pointer allocate(size_type n = 1, const_pointer hint = 0); void deallocate(pointer p, size_type n = 1); size_type max_size() const noexcept; template <class U, class... Args> void construct(U* p, Args&&... args); template <class U> void destroy(U* p); template <class... Args> pointer newElement(Args&&... args); void deleteElement(pointer p); private: struct Element_{ Element_* pre; Element_* next; }; typedef char* data_pointer; typedef Element_ element_type; typedef Element_* element_pointer; element_pointer data_element_; element_pointer free_element_; std::recursive_mutex m_; size_type padPointer(data_pointer p, size_type align) const noexcept; void allocateBlock(); static_assert(BlockSize >= 2 * sizeof(element_type), "BlockSize too small."); }; template <typename T, size_t BlockSize, bool ZeroOnDeallocate> inline typename MemoryPool<T, BlockSize, ZeroOnDeallocate>::size_type