Skip to content

LomanTrue/MemoryPoolAllocator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 

Repository files navigation

MemoryPoolAllocator

STL-совместимый аллокатор памяти на C++17 с пулами блоков разного размера. Заранее резервирует крупные области памяти и выдаёт из них блоки, избегая частых обращений к системному new/delete. Размеры блоков задаются параметрами шаблона, поэтому конфигурация пулов фиксируется на этапе компиляции.

Header-only: достаточно подключить один заголовок.

Идея

Классический std::allocator под капотом каждый раз дёргает системный аллокатор. Для контейнеров с большим числом мелких аллокаций это дорого. Pool allocator решает это так:

  • при создании выделяет несколько больших непрерывных пулов памяти;
  • каждый пул разбит на блоки фиксированного размера;
  • запрос на выделение обслуживается поиском нужного числа подряд идущих свободных блоков;
  • освобождение просто помечает блоки свободными — без обращения к ОС.

Несколько пулов с разными размерами блоков позволяют эффективнее обслуживать запросы разного объёма: маленькие объекты идут в пул с мелкими блоками, крупные — в пул с большими.

Использование

#include "allocator.h"
#include <deque>

int main() {
    // Пул на 100000 байт, блоки по 8 байт.
    std::deque<int, MemoryPoolAllocator<int, 100000, 8>> dq;
    for (int i = 0; i < 10000; ++i) {
        dq.push_back(i);
    }
}

Несколько пулов разного размера блока (16, 32 и 64 байта):

std::vector<double, MemoryPoolAllocator<double, 100000, 16, 32, 64>> v;
v.reserve(1000);
for (int i = 0; i < 1000; ++i) v.push_back(i * 1.5);

Параметры шаблона

MemoryPoolAllocator<T, pool_size, first_block_size, block_sizes...>
Параметр Назначение
T Тип элемента (как у любого аллокатора)
pool_size Размер каждого пула в байтах
first_block_size Размер блока первого пула
block_sizes... Размеры блоков остальных пулов (variadic)

На каждый размер блока создаётся отдельный пул на pool_size байт.

Устройство

Проект состоит из двух классов в MemoryPoolAllocator.h:

MemoryPool — владеет одной непрерывной областью памяти, разбитой на блоки фиксированного размера. Хранит битовую карту занятости и умеет выдавать несколько подряд идущих свободных блоков под запрос в байтах. Класс владеет сырой памятью, поэтому копирование запрещено, а перемещение реализовано (rule of five).

MemoryPoolAllocator — STL-совместимый аллокатор поверх вектора пулов. Реализует allocate / deallocate, rebind, operator==, необходимые typedef'ы. При выделении перебирает пулы и отдаёт память из первого подходящего; при освобождении находит пул-владелец по диапазону адресов.

Пулы хранятся под общим std::shared_ptr, поэтому копии аллокатора (а STL копирует аллокаторы, в том числе через rebind) разделяют одну память — это исключает двойное освобождение и use-after-free при перепривязке.

Ограничения

Аллокатор плохо подходит для std::vector. vector хранит данные в одном непрерывном буфере и при росте удваивает его, запрашивая всё больший непрерывный кусок. Как только нужный размер превысит pool_size, выделение завершится std::bad_alloc — ни один пул не сможет выдать столько подряд.

Например, для std::vector<int, MemoryPoolAllocator<int, 32000, 4, 8>> заполнение 5000 элементами упадёт: при росте capacity до 8192 элементов vector запросит 8192 × 4 = 32768 байт — больше, чем пул в 32000 байт.

Что с этим делать:

  • использовать контейнеры, которые аллоцируют память кусками, а не одним буфером — std::deque, std::list, std::map;
  • для vector — вызывать reserve() заранее, чтобы избежать удвоения (тогда единственный буфер должен помещаться в пул);
  • увеличить pool_size так, чтобы он вмещал пиковый буфер контейнера.

Выравнивание не учитывается. Блоки нарезаются побайтово, и возвращаемый указатель выровнен только по границе блока. Для типов с alignof(T), превышающим фактическое выравнивание блока (например, SIMD-типы), это формально некорректно. Для стандартных скалярных типов (int, double, указатели) при разумных размерах блоков работает корректно.

Сборка

Header-only — отдельная сборка не требуется, достаточно подключить заголовок:

#include "allocator.h"

Требуется компилятор с поддержкой C++17 (используются fold expressions). Проверено на GCC и Clang, собирается без предупреждений с -Wall -Wextra, проходит проверку AddressSanitizer и UBSanitizer на C++17 и C++20.

Пример сборки демо:

g++ -std=c++17 -Wall -Wextra main.cpp -o demo

Технические детали реализации

  • Variadic-шаблоны для конфигурации произвольного числа пулов
  • Fold expression (C++17) для создания пулов из пакета параметров на этапе компиляции
  • Rule of five у MemoryPool: запрет копирования, noexcept-перемещение (нужно, чтобы std::vector мог перемещать пулы при росте)
  • std::shared_ptr для разделяемого владения пулами между копиями аллокатора
  • Поиск свободной серии блоков по битовой карте; освобождение за O(1) по вычислению индекса из адреса

About

Realization of allocator with pool memory

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages