Как boost.pool обеспечивает повторное использование выделенной памяти?

Фон

Мой предыдущий вопрос о boost.pool побудил меня детально изучить boost.pool, и теперь у меня есть дополнительный вопрос, чтобы завершить мое понимание.

прелюдия

В этой ссылке говорится о шаблоне пула объектов:

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

Из того, что я могу сказать, boost.pool (упрощенно) реализует шаблон пула объектов путем выделения памяти и управления ею, в основном на основе размера element_typeи возвращает простой указатель на выделенный объект:

element_type * malloc();
void free(element_type * p);

Простой пример повышения также показывает, что нет необходимости явно free приобретенный элемент:

X * const t = p.malloc();
... // Do something with t; don't take the time to free() it.

Вопрос

Я понимаю, что выделенная память будет безопасно освобождена при уничтожении объекта пула, но как пул узнает, когда блок памяти, полученный клиентом, был освобожден обратно в пул и может использоваться повторно, если его интерфейс возвращает прямой указатель в element_typeеще звонок free() все еще не требуется? т.е. как пул повышения может повторно использовать эту память, если не может быть уверенности, что память все еще не используется? И если он не использует эту память повторно, считается ли это тем же шаблоном, который описан в вики-справке?

2 ответа

Решение

Как пул форсирования может повторно использовать эту память, если нет уверенности, что она еще не используется?

Не может На самом деле это не будет повторно использовать эту память. Это только гарантирует, что у вас не будет утечек, когда бассейн будет разрушен.

И если он не использует эту память повторно, считается ли это тем же шаблоном, который описан в вики-справке?

В статье, на которую вы ссылаетесь, говорится: пул объектов может значительно повысить производительность в ситуациях, когда высока стоимость инициализации экземпляра класса.

В то время как из введения Boost pool: Пулы обычно используются, когда есть много выделения и освобождения небольших объектов.

Так что нет, они не по одной схеме. Один предназначен для повторного использования дорогостоящих объектов (потоков, ресурсов opengl и т. Д.). Другой предназначен для управления множеством мелких объектов, что дает вам больше контроля, чем дает стандартный распределитель.

Как вы указали, есть два способа использования пулов:

  1. В качестве распределителя, вызывая malloc()/free() при необходимости. Это основное использование пула-распределителя, оно помогает уменьшить фрагментацию памяти
  2. Создайте тонну временных объектов и не пытайтесь их удалить.

Пример для второго случая: представьте, что у вас есть класс графа, где каждый узел хранит своих соседей, используя указатели. Теперь вы должны сделать глубокую копию вашего графика. Вы выделите кучу новых узлов и скопируете данные из старых в новые, но теперь вам нужно инициализировать указатели соседей, так что вам нужна карта от старых указателей к новым указателям:

std::map<node*,node*> old_ptr_to_new_ptr;

Это хороший пример использования распределителей пула (я не буду вдаваться в подробности о том, как использовать распределители пула с контейнерами std): множество небольших объектов (узлов карты), которые будут удалены все вместе.

Библиотека пулов наддува предоставляет распределители STL, которые более эффективны при распределении объектов одного типа (в отличие от std::allocator который просто использует new а также delete). Это то, что Страуструп или Александреску назвали бы распределителем малых объектов.

Как и любой пользовательский класс-распределитель, он работает с четырьмя отдельными функциями: выделять, освобождать, конструировать и уничтожать. Я думаю, что их имена говорят сами за себя (если вы не путаетесь в распределении или строительстве). Чтобы получить новый объект из пула, вы сначала вызываете allocate(1) чтобы получить указатель, а затем вы звоните construct( ptr, value ) по этому указателю ptr чтобы получить его как копию value (или переехал). И вы делаете наоборот, когда вы хотите удалить этот объект. Это механизм, который все контейнеры STL используют скрытно для выделения-конструирования-уничтожения-освобождения своих объектов.

Вы не должны доверять статье в Википедии, на которую вы ссылались (и не в целом), она очень плохо сформулирована, использует очень расплывчатый и неточный язык и принимает несколько узкий взгляд на шаблон пула объектов. И между прочим, цитирование Википедии бесполезно, вы не знаете, кто это написал, нет причин доверять этому, всегда переходите к источнику.

Шаблон, описанный в вики (и особенно в исходной статье), имеет цель, совершенно отличную от той, которую пытаются выполнить распределители пула поддержки. Как описано в вики, упор делается на повторное использование объектов без их реального уничтожения (например, пул потоков является хорошим примером, потому что было бы дорого создавать и уничтожать потоки часто, а исходная статья заинтересована в объединении службы базы данных поставщики по аналогичным причинам). В распределителях повышающего пула акцент делается на том, чтобы избегать вызова кучи (freestore) для выделения множества небольших объектов - задача, которую куча не может выполнить очень эффективно и приведет к ее фрагментации. Возможно, вместо этого его следует называть "распределителями небольших объектов", чтобы избежать путаницы.

как пул узнает, когда блок памяти, полученный клиентом, был освобожден обратно в пул и может использоваться повторно, если его интерфейс возвращает прямой указатель на element_type, но при этом вызов free () по-прежнему не требуется?

Я не думаю, что это может. Я считаю, что история идет так. Вы можете просто выделить кучу объектов из пула, никогда не освобождая их, и это все еще безопасно в том смысле, что когда вы уничтожаете пул-распределитель, вся его память сбрасывается вместе с ним, включая все объекты, которые вы оставшись в бассейне. Это то, что они подразумевают под "не требуется освобождать объекты", просто, что ваше приложение не будет пропускать память после истечения срока службы объекта пул-распределитель, если вы забудете освободить все объекты из пула.

Но если вы не скажете пулу-распределителю освободить объекты, которые вам больше не нужны (и, следовательно, могут быть повторно использованы), он не сможет повторно использовать эти слоты памяти, это просто невозможно (учитывая, что распределители не • доставить любой специальный смарт-указатель, который сможет отслеживать выделенные объекты).

Как пул форсирования может повторно использовать эту память, если нет уверенности, что она еще не используется?

Если нет уверенности в том, что память все еще не используется, то нет способа повторно использовать память. Любой фрагмент кода, который может привести к такой безрассудной вещи, как "предположить, что объект больше не нужен, не будучи уверенным", будет бесполезным фрагментом кода, поскольку он, очевидно, будет иметь неопределенное поведение, и никакой программист не сможет его использовать когда-либо.

И если он не использует эту память повторно, считается ли это тем же шаблоном, который описан в вики-справке?

Нет, он не реализует то, что объясняется в вики. Вам придется привыкнуть к тому факту, что терминология иногда сталкивается неудачно. Чаще всего ссылаются на то, что Boost Pool реализует либо как "пул памяти", либо как "распределитель небольших объектов". Эта структура оптимизирована для небольших объектов, которые довольно дешево создавать и копировать. Поскольку куча (freestore) предназначена для больших блоков памяти и имеет тенденцию плохо справляться с попытками найти место для небольших объектов, использование ее для этой цели не является хорошей идеей и может привести к фрагментации кучи. Таким образом, пул-распределители по существу заменяют кучу чем-то, что более эффективно при распределении множества небольших объектов одного типа. Он не использует объекты повторно, но может повторно использовать освобожденную память, как это делает куча. И он обычно выделяет свою память (которую он выделяет) из кучи как большой непрерывный блок (например, с std::vector). Есть много других преимуществ производительности при использовании распределителя небольших объектов, когда это уместно. Но то, что реализует Boost Pool, на самом деле сильно отличается от того, что описано в вики. Вот описание того, для чего хороши пул-распределители:

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

Другие вопросы по тегам