Убедительные примеры пользовательских C ++-распределителей? - PullRequest
162 голосов
/ 05 мая 2009

Какие действительно веские причины отказаться от std::allocator в пользу нестандартного решения? Сталкивались ли вы с ситуациями, когда это было абсолютно необходимо для корректности, производительности, масштабируемости и т. Д.? Какие-нибудь действительно умные примеры?

Пользовательские распределители всегда были функцией Стандартной библиотеки, в которой я не особенно нуждался. Мне просто интересно, может ли кто-нибудь здесь, на SO, привести несколько убедительных примеров, оправдывающих их существование.

Ответы [ 16 ]

3 голосов
/ 17 марта 2012

Одна существенная ситуация: при написании кода, который должен работать через границы модулей (EXE / DLL), важно, чтобы ваши выделения и удаления происходили только в одном модуле.

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

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

2 голосов
/ 05 ноября 2017

Некоторое время назад я нашел это решение очень полезным для меня: Быстрый C ++ 11 распределитель для контейнеров STL . Это немного ускоряет контейнеры STL на VS2017 (~ 5x), а также на GCC (~ 7x). Это распределитель специального назначения, основанный на пуле памяти. Он может использоваться с контейнерами STL только благодаря запрашиваемому вами механизму.

2 голосов
/ 15 января 2016

Обязательная ссылка на доклад Андрея Александреску на CppCon 2015 о распределителях:

https://www.youtube.com/watch?v=LIb3L4vKZ7U

Приятно то, что их разработка заставляет задуматься о том, как их использовать: -)

2 голосов
/ 25 мая 2015

Для разделяемой памяти жизненно важно, чтобы не только головка контейнера, но и содержащиеся в нем данные сохранялись в разделяемой памяти.

Распределитель Boost :: Interprocess является хорошим примером. Однако, как вы можете прочитать здесь этого всего недостаточно, чтобы совместить совместную память всех контейнеров STL (из-за разных смещений отображения в разных процессах указатели могут «сломаться»).

1 голос
/ 15 января 2016

В графической симуляции я видел пользовательские распределители, используемые для

  1. Ограничения выравнивания, которые std::allocator напрямую не поддерживали.
  2. Минимизация фрагментации за счет использования отдельных пулов для кратковременных (только этот кадр) и долгоживущих распределений.
1 голос
/ 02 января 2015

Я лично использую Loki :: Allocator / SmallObject для оптимизации использования памяти для небольших объектов - он показывает хорошую эффективность и удовлетворительную производительность, если вам приходится работать с умеренным количеством действительно маленьких объектов (от 1 до 256 байт). Это может быть в ~ 30 раз более эффективно, чем стандартное распределение C ++ new / delete, если мы говорим о выделении умеренного количества небольших объектов разных размеров. Кроме того, есть решение для VC под названием «QuickHeap», оно обеспечивает наилучшую возможную производительность (операции выделения и освобождения просто читают и записывают адрес блока, который выделяется / возвращается в кучу, соответственно, в 99%. (9)% случаев - зависит от настроек и инициализации), но за счет значительных накладных расходов - для него требуется два указателя на экстент и один дополнительный для каждого нового блока памяти. Это самое быстрое решение для работы с огромным (10 000 ++) количеством создаваемых и удаляемых объектов, если вам не нужно большое разнообразие размеров объектов (для каждого размера объекта создается отдельный пул от 1 до 1023 байт). в текущей реализации затраты на инициализацию могут свести на нет общее повышение производительности, но можно пойти дальше и выделить / освободить некоторые фиктивные объекты до того, как приложение войдет в фазу (ы), критичные для производительности).

Проблема со стандартной реализацией C ++ new / delete заключается в том, что обычно это просто оболочка для выделения памяти malloc / free, и она хорошо работает для больших блоков памяти, таких как 1024+ байта. Он имеет заметные накладные расходы с точки зрения производительности и, иногда, дополнительной памяти, используемой для отображения. Таким образом, в большинстве случаев пользовательские распределители реализованы таким образом, чтобы максимизировать производительность и / или минимизировать объем дополнительной памяти, необходимой для выделения небольших (≤1024 байтов) объектов.

...