6

Я изучаю переключение метода выделения из упрощенной перегрузки нового на использование нескольких распределителей через базу кода. Однако как я могу эффективно использовать несколько распределителей? Единственный способ, который я мог бы разработать в своих исследованиях, заключался в том, чтобы распределители были глобальными. Хотя у этого, казалось, были проблемы, поскольку обычно это «плохая идея» использовать многие глобальные переменные.Эффективное использование нескольких распределителей

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

+2

Почему распределитель должен быть глобальным? Пока каждый выделенный блок имеет ссылку на свой собственный распределитель, чтобы его можно было правильно освободить, имеет ли значение, где собственно распределитель? –

+0

Где бы распределитель ушел на выделенную единицу? Мне кажется, что это должно быть глобально. – chadb

ответ

8

В C++ 2003 модель распределителя сломана, и на самом деле нет правильного решения. Для C++ 2011 модель распределителя была исправлена, и вы можете иметь для каждого распределителя экземпляров, которые распространяются до содержащихся объектов (если, конечно, вы не захотите их заменить). Как правило, для того, чтобы это было полезно, вы, вероятно, захотите использовать динамически полиморфный тип распределителя, который по умолчанию не нужен std::allocator<T> (и обычно я ожидал бы, что он не будет динамически полиморфным, хотя это может быть лучшим выбором реализации). Тем не менее, [почти] все классы стандартной библиотеки C++, которые занимают выделение памяти, являются шаблонами, которые используют тип распределителя как аргумент шаблона (например, IOStreams являются исключением, но обычно они не выделяют никакого интересного количества памяти, чтобы гарантировать добавление поддержки распределителя).

В нескольких ваших комментариях вы настаиваете на том, что распределители действительно должны быть глобальными: это определенно неверно. Каждый тип, поддерживающий распределитель, хранит копию предоставленного распределителя (по крайней мере, если у него есть данные уровня экземпляра, а если нет, то нет ничего, что можно было бы сохранить, например, в случае с распределителем по умолчанию с использованием operator new() и operator delete()) , Это фактически означает, что механизм распределения, предоставляемый объекту, должен придерживаться до тех пор, пока он использует активный распределитель. Этот может быть выполнен с использованием глобального объекта, но он также может быть выполнен с использованием, например, подсчет ссылок или связывание распределителя с объектом, содержащим все объекты, которым он задан. Например, если каждый «документ» (думаю, XML, Excel, Pages, любой файл структуры) передает распределитель своим членам, распределитель может жить как член документа и уничтожаться, когда документ уничтожается после уничтожения его содержимого , Эта часть модели распределителя должна работать с классами pre-C++ 2011, если они также принимают аргумент распределителя. Однако в классах pre-C++ 2011 распределитель не будет передаваться содержащимся объектам. Например, если вы предоставите распределитель для std::vector<std::string>, версия C++ 2011 создаст std::string с использованием распределителя, присвоенного std::vector<std::string>, соответствующим образом преобразованным в сделку с std::string с.Этого не произойдет с распределителями pre-C++ 2011.

Чтобы на самом деле использовать распределители в подсистеме, вам действительно необходимо передать их, либо явно, как аргумент для ваших функций, и/или классов, либо неявно с помощью объектов-распределителей, которые служат в качестве контекста. Например, если вы используете какой-либо из стандартных контейнеров в качестве [части] контекста, который передается, вы можете получить использованный распределитель, используя его метод get_allocator().

+0

Очень интересно, я не думал о подсчете ссылок. Поскольку я буду создавать свой собственный распределитель, должен ли я наследовать его от чего-то вроде weak_reference, чтобы можно было подсчитать ссылку? – chadb

+1

Лично я хотел бы использовать 'std :: shared_ptr ' как член распределителя 'my_allocator'. Фактическая логика распределения будет располагаться в классах, полученных из 'my_allocation_base'. Если у вас есть только один подход к распределению, вы можете, конечно, поместить логику непосредственно в объект с указателем на объект. Использование 'weak_reference' (не знаю, что это такое) звучит так, как будто это не работает: вы хотите иметь фактическую ссылку на объект выделения из каждого объекта, все еще удерживающего соответствующую выделенную память, и счетчик ссылок будет уменьшен после его выпущенный. –

2

Вы можете использовать размещение new. Это можно использовать либо для указания области памяти, либо для перегрузки типа static void* operator new(ARGS). Глобалы не требуются, и действительно плохая идея здесь, если эффективность важна и ваши проблемы требуют. Конечно, вам нужно будет держаться за одного или нескольких распределителей.

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

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

+0

Как следует проводить распределители? Вы упомянули, что они не должны быть глобальными, но где же тогда быть? (Кроме того, меня не касается совместимости со стандартными контейнерами, поскольку у меня уже есть). – chadb

+0

@chadb Это зависит от проблемы. Я использую ссылку на оба элемента (например, с подсчитанными подсчетами) и внешнюю ссылку (например, распределитель для графика, все узлы которого управляются одним распределителем). Локальные распределители потоков (доступ к потоку или его данным) являются другим подходом, хотя в этом случае я предпочитаю внешнюю ссылку. – justin

+0

Что касается специально для распределителя, используемого для всей подсистемы (корень которой не находится в одноэлементном)? Кажется, это мой основной случай использования на данный момент, как я упоминал в почтовом ящике, который, как я полагаю. Где будет храниться этот распределитель? Я не могу думать ни о каком деле, кроме глобального. – chadb

1

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

Использование ЦП будет улучшено за счет отсутствия блокировки для активных потоков, что исключает синхронизацию. Это можно сделать в распределителе памяти с локальным хранилищем потоков.

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

Недостатки кэша будут улучшены путем сохранения распределений внутри системы вместе. Если выделение Quadtree/Octree происходит из собственной кучи, это гарантирует наличие местностей с учетом запросов frustrum. Это лучше всего сделать, перегружая оператор new и оператор delete для определенных классов (OctreeNode).

+0

Возможно, что-то, что неверно истолковало, однако, мой вопрос состоял в основном в том, как использовать несколько распределителей, а не почему. – chadb