Я использую класс пула памяти, который повторно использует выделенные адреса памяти и пользовательский распределитель, который переносит
этот класс. Следующий фрагмент кода дает базовое представление об интерфейсе.
template<class alloc>
class memory_pool
: boost::noncopyable,
public allocator_traits<void>
{
public:
memory_pool(typename alloc::size_type alloc_size);
memory_pool(typename alloc::size_type alloc_size, alloc const&);
template<typename U> memory_pool(typename alloc::size_type alloc_size,
typename alloc::rebind<U>::other const&);
virtual ~memory_pool();
pointer allocate (); /*throw(std::bad_alloc)*/
void collect ();
void deallocate(pointer) throw(); /*noexcept*/
};
pointer allocate()
{/*
Checks if a suitable chunk of memory is available in a internal linked list.
If true, then the chunk is returned and the next chunk moves up.
Otherwise, new memory is allocated by the underlying allocator.
*/}
void deallocate(pointer)
{/*
Interprets the passed pointer as a chunk of memory and stores it in a linked list.
Please note that memory isn't actually deallocated.
*/}
void collect()
{/*
Effectively deallocates the cunks in the linked list.
This will be called at least once during destruction.
*/}
Конечно, необходимость чего-то подобного ограничена. Тем не менее, это очень полезно в ситуациях, когда вам нужно
чтобы:
- Определить тип распределителя для класса, который использует этот распределитель очень наивно (например, избегает
выделение больших кусков, даже если это будет целесообразно).
- Выделяйте и освобождайте повторно один и тот же размер памяти.
- Тип, для которого вы хотите использовать распределитель, имеет очень маленький размер (например, встроенные типы, такие как char, short, int и т. Д.).
Теоретически, реализация может использовать преимущество memory_pool, который выделяет кратное фактического размера выделения, каждый раз, когда это необходимо сделать (из основного менеджера памяти). Объекты, расположенные близко друг к другу, больше подходят для любого алгоритма кэширования и / или предварительной выборки.
Я реализовал такой пул памяти с некоторыми накладными расходами для обработки правильного выделения, разделения и освобождения (мы не можем освободить каждый адрес, который пользователь передаст на освобождение. Нам нужно освободить только те адреса, которые являются началом каждого блока памяти, который у нас есть был ранее выделен).
Я протестировал оба случая со следующим действительно простым кодом:
std::list<int, allocator<int>> list;
std::clock_t t = std::clock();
for (int i = 0; i < 1 << 16; ++i)
{
for (int j = 0; j < 1 << 16; ++j)
list.push_back(j);
list.unique();
for (int j = 0; j < 1 << 16; ++j)
list.pop_back();
}
std::cout << (std::clock() - t) / CLOCKS_PER_SEC << std::endl;
std::list
звонит allocactor::allocate(1, 0)
каждый раз, когда push_back
звонит. unique()
гарантирует, что каждый элемент будет затронут и сравнен со следующим элементом.
Однако результат оказался неутешительным. Минимальные накладные расходы, необходимые для управления пулом памяти, распределенным по блокам, превышают любые возможные преимущества, которые получает система.
Можете ли вы вспомнить сценарий, в котором это улучшит производительность?
EDIT:
Конечно, это намного быстрее, чем std::allocator
.