Пользовательский распределитель C ++, который использует базовый пул памяти

Я использую класс пула памяти, который повторно использует выделенные адреса памяти, и настраиваемый распределитель, который обертывает этот класс. Следующий фрагмент кода дает вам общее представление об интерфейсе.

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() гарантирует, что каждый элемент будет затронут и сравнен со следующим элементом. Однако результат оказался неутешительным. Минимальные накладные расходы, необходимые для управления пулом памяти с поблочным распределением, превышают любое возможное преимущество, которое получает система.

Можете ли вы придумать сценарий, в котором это улучшит производительность?

РЕДАКТИРОВАТЬ: Конечно, это намного быстрее, чем std::allocator.


person 0xbadf00d    schedule 06.07.2011    source источник
comment
Обратите внимание, что распределитель упаковки не может выделить массив.   -  person 0xbadf00d    schedule 06.07.2011


Ответы (2)


C ++ 0x лучше поддерживает распределители с ограниченной областью действия типа пулов памяти.

Профилируйте свой код. очень трудно предсказать, какие преимущества это принесет, если только ваш алгоритм не выполняет очень регулярные шаблоны выделения / освобождения, такие как LIFO.

Очень легко написать очень быстрый распределитель, когда все выделенные объекты имеют одинаковый размер. Однажды я написал что-то вроде

template <size_t ObjectSize> class allocator {
    // ...
};

template <typename T> class allocator : public allocator <sizeof (T)> {
    // ...
};

Прежде чем вы сможете спроектировать распределитель, вы должны быть уверены, что и как будет распределяться. Ответы на operator new: «что угодно» и «так или иначе», поэтому иногда это неоптимально. Если вы не можете правильно ответить на эти вопросы, ваш распределитель, вероятно, не сильно улучшится.

person spraff    schedule 06.07.2011
comment
Цитата из вашей ссылки: можно использовать распределитель с состоянием, скажем, распределитель, который содержит указатель на арену, из которой следует выделить. Почему это должно быть невозможно с C ++ 03? - person 0xbadf00d; 06.07.2011
comment
По крайней мере, для STL распределители определены для каждого типа, а не для каждого экземпляра. Вы можете написать свои собственные распределители с отслеживанием состояния, но они нарушат установленную реализацию библиотеки, если использовать их в std::vector или в одном из ее собратьев. - person spraff; 06.07.2011
comment
Я делал это раньше (используя ObjectSize как выражение времени компиляции). Приятно читать, но ограничивает использование. Вы могли заметить, что я работаю только с void* на этом этапе. - person 0xbadf00d; 06.07.2011

Можете ли вы придумать сценарий, в котором это улучшит производительность?

Что-то, что делает много (10k + в секунду) выделения и освобождения, выиграет от того, что вам не придется каждый раз сталкиваться со сложными запросами для выделения / освобождения, однако это только в том случае, если объединенная экономия на задержке выделения / освобождения в группы больше, чем это требуется для обработки группы (в основном вам нужно амортизировать группу с сохранением на единицу).

использование непрерывной памяти поможет любым структурам, основанным на узлах / указателях, таких как деревья (но только до определенной точки). однако реальные преимущества могут резко отличаться (или отсутствовать!) от того, что они планировали получить, поэтому, идя по пути создания таких пользовательских систем, вы должны профилировать свой код и уже иметь Представьте себе, как он будет использоваться (например, нет смысла создавать специальный распределитель пула для чего-то, что делает так мало распределений, что прирост скорости вообще не имеет значения).

Однако что-то подобное может быть удобно для отладки, поскольку у вас есть хороший интерфейс для маркировки памяти для мониторинга утечек и перезаписи / недопустимой записи, поэтому, даже если она имеет такую ​​же производительность, как стандартная система, вы можете получить другие способы.

person Necrolis    schedule 06.07.2011