Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Architecture and internals

Boost , The Boost C++ Libraries BoostBook Documentation Subset , Chapter 16. Boost.Interprocess

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

При строительствеBoost.InterprocessАрхитектура, я взял некоторые основные принципы, которые можно суммировать по этим пунктам:

  • Boost.Interprocessдолжны быть портативными, по крайней мере, в системах UNIX и Windows. Это означает объединение не только интерфейсов, но и поведения. Вот почемуBoost.Interprocessвыбрал ядро или файловую систему для совместно используемой памяти и назвал механизмы синхронизации. Сохранение процесса для совместно используемой памяти также желательно, но его трудно достичь в системах UNIX.
  • Усиление.ИнтерпроцессПримитивы межпроцессной синхронизации должны быть равны примитивам синхронизации потоков.Boost.Interprocessстремится иметь интерфейс, совместимый со стандартным API потоков C++.
  • Архитектура процессадолжна быть модульной, настраиваемой, но эффективной. Вот почемуBoost.Interprocessоснован на шаблонах и алгоритмах памяти, типы индексов, типы mutex и другие классы являются временными.
  • Архитектура процессадолжна обеспечивать ту же параллель, что и программирование на основе потоков. Различные уровни взаимного исключения определяются таким образом, что процесс может одновременно выделять сырую память при расширении вектора общей памяти, в то время как другой процесс может безопасно искать названный объект.
  • Boost.Interprocessконтейнеры ничего не знают оBoost.Interprocess. Все специфическое поведение содержится в STL-подобных распределителях. Это позволяет поставщикам STL слегка модифицировать (или, лучше сказать, обобщить) свои стандартные реализации контейнеров и получить полностью std::allocator и повысить::interprocess:::allocator совместимый контейнер. Это также делает контейнерыBoost.Interprocessсовместимыми со стандартными алгоритмами.

Boost.Interprocessпостроен выше 3 основных классов: алгоритмпамяти, менеджер сегментови сегмент управляемой памяти:

Алгоритмпамятипредставляет собой объект, который помещается в первые байты разделяемого сегмента файла памяти/памяти. Алгоритмпамятиможет возвращать части этого сегмента пользователям, помечающим их как используемые, и пользователь может возвращать эти части алгоритмупамятитак, чтобы алгоритмпамятиснова помечал их как свободные. Однако есть исключение: некоторые байты за пределами конца объекта алгоритма памяти зарезервированы и не могут быть использованы для этого динамического распределения. Эта «зарезервированная» зона будет использоваться для размещения других дополнительных объектов в хорошо известном месте.

Подводя итог, можно сказать, что алгоритмпамятиимеет ту же миссию, что и malloc/free стандартной библиотеки C, но он просто может возвращать части сегмента, где он размещен. Компоновка сегмента памяти будет:

Layout of the memory segment:
 ____________ __________ ____________________________________________
|            |          |                                            |
|   memory   | reserved |  The memory algorithm will return portions |
| algorithm  |          |  of the rest of the segment.               |
|____________|__________|____________________________________________|

Алгоритмпамятизаботится о синхронизации памяти, точно так же, как malloc/free гарантирует, что два потока могут вызывать malloc/free одновременно. Обычно это достигается путем размещения мутекса, разделяемого процессом, в качестве элемента алгоритма памяти. Обратите внимание, что алгоритм памяти ничего не знает о сегменте(если это общая память, файл общей памяти и т.д.). Для алгоритма памяти сегмент представляет собой буфер памяти фиксированного размера.

Алгоритмпамятитакже является точкой конфигурации для остальной части.Boost.Interprocessframework, поскольку он определяет два основных типа в качестве типовых членов:

typedef /*implementation dependent*/ void_pointer;
typedef /*implementation dependent*/ mutex_family;

<void_pointer>typedef определяет тип указателя, который будет использоваться вBoost.Interprocessframework (сегмент-менеджер, распределители, контейнеры). Если алгоритм памяти готов к размещению в файле общей памяти, отображаемом в разных базовых адресах, этот тип указателя будет определен как<offset_ptr<void>>или аналогичный относительный указатель. Если алгоритмпамятибудет использоваться только с фиксированным отображением адресов,<void_pointer>можно определить как<void*>.

Остальная часть интерфейсаBoost.Interprocessалгоритма памятиописана вНаписание нового раздела алгоритма распределения общей памяти. В качестве примеров алгоритма памяти можно увидеть реализации<simple_seq_fit>или<rbtree_best_fit>классов.

Менеджер сегментов, является объектом, также помещенным в первые байты сегмента управляемой памяти (общая память, файл с картой памяти), который предлагает более сложные услуги, построенные над алгоритмомпамяти. Какименеджер сегментов и алгоритм памяти могут быть размещены в начале сегмента? Это потому, что менеджер сегментоввладееталгоритмом памяти: Правда в том, что алгоритм памятивстроенв менеджер сегментов:

The layout of managed memory segment:
 _______ _________________
|       |         |       |
| some  | memory  | other |<- The memory algorithm considers
|members|algorithm|members|   "other members" as reserved memory, so
|_______|_________|_______|   it does not use it for dynamic allocation.
|_________________________|____________________________________________
|                         |                                            |
|    segment manager      |  The memory algorithm will return portions |
|                         |  of the rest of the segment.               |
|_________________________|____________________________________________|

Менеджер сегментовинициализирует алгоритм памяти и сообщает менеджеру памяти, что он не должен использовать память, где остальная частьчлена сегментаразмещена для динамических распределений. Другие членыменеджера сегментовявляютсярекурсивным мутексом(определяется алгоритмом памятиmutex_family::recursive_mutexтипизированным членом), идвумя индексами (картами): один для реализации именованных распределений, а другой для реализации распределений «уникального экземпляра».

  • Первым индексом является карта с указателем на c-струну (название названного объекта) в качестве ключа и структура с информацией о динамически выделенном объекте (наиболее важным является адрес и размер объекта).
  • Второй индекс используется для реализации «уникальных экземпляров» и в основном совпадает с первым индексом, но название объекта происходит от операции<typeid(T).name()>.

Память, необходимая для хранения пар [указатель имени, информация об объекте] в индексе, выделяется также с помощью алгоритмапамяти, поэтому мы можем сказать, что внутренние индексы аналогичны обычным пользовательским объектам, построенным в сегменте. Остальная часть памяти для хранения имени объекта, самого объекта и метаданных для уничтожения/выведения выделяется с помощью алгоритмапамятив одном<allocate()>вызове.

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

Менеджер сегментареализован вBoost.Interprocessпо классу<segment_manager>.

template<class CharType
        ,class MemoryAlgorithm
        ,template<class IndexConfig> class IndexType>
class segment_manager;

Как видно, менеджер сегментов довольно общий: мы можем указать тип символа, который будет использоваться для идентификации названных объектов, мы можем указать алгоритм памяти, который будет динамически управлять частями сегмента памяти, и мы можем также указать тип индекса, который будет хранить отображение [указатель имени, информация об объекте]. Мы можем создавать собственные типы индексов, как описано в. Создание пользовательских индексовраздел.

Boost.Interprocessуправлял сегментами памяти, которые конструировали общий файл, отображаемый в памяти/памяти, размещали там менеджер сегментов и пересылали пользовательские запросы менеджеру сегментов. Например,<basic_managed_shared_memory>является.Boost.Interprocessуправляет сегментом памяти, который работает с общей памятью.<basic_managed_mapped_file>работает с картированными файлами памяти и т.д.

Интерфейс сегмента управляемой памятиBoost.Interprocessтакой же, как и сегмент-менеджер, но он также предлагает функции для «открытия», «создания» или «открытия или создания» разделов совместно используемой памяти / файлов с памятью и инициализации всех необходимых ресурсов. Управляемые классы сегментов памяти не встроены в общую память или картированные файлы памяти, они являются обычными классами C++, которые хранят указатель на менеджер сегментов (который встроен в общую память или картированные файлы памяти).

Кроме того, управляемые сегменты памяти предлагают определенные функции:<managed_mapped_file>предлагает функции для смывания содержимого памяти в файл,<managed_heap_memory>предлагает функции для расширения памяти и т. д.

Большая часть функцийBoost.Interprocessуправляемых сегментов памяти может быть разделена между всеми управляемыми сегментами памяти, поскольку во многих случаях они просто перенаправляют функции менеджеру сегментов. Из-за этого вBoost.Interprocessвсе сегменты управляемой памяти происходят из общего класса, который реализует функции, не зависящие от памяти (общая память, файлы, отображаемые в памяти):boost::interprocess::ipcdetail::basic_managed_memory_impl

Происхождение этого классаBoost.Interprocessреализует несколько управляемых классов памяти для различных бэкэндов памяти:

Boost.InterprocessSTL-подобные распределители довольно просты и следуют обычному подходу C++ allocator. Как правило, распределители контейнеров STL базируются выше новых / удаленных операторов и выше них, они реализуют пулы, арены и другие приемы распределения.

ВBoost.Interprocessаллокаторы подход аналогичен, но все аллокаторы основаны наменеджере сегмента. Менеджер сегментов — единственный, который обеспечивает от простого выделения памяти до именованных объектов.Boost.Interprocessраспределители всегда хранят указатель на менеджер сегмента, чтобы они могли получать память из сегмента или совместно использовать общий пул между распределителями.

Как вы можете себе представить, указатели членов распределителя — это не необработанные указатели, а типы указателей, определенные типом<segment_manager::void_pointer>. Кроме того,<pointer>типдефBoost.Interprocessаллокаторы также имеют тот же тип<segment_manager::void_pointer>.

Это означает, что если наш алгоритм распределения определяет<void_pointer>как<offset_ptr<void>>,<boost::interprocess::allocator<T>>будет хранить<offset_ptr<segment_manager>>, чтобы указать на менеджер сегмента, а тип<boost::interprocess::allocator<T>::pointer>будет<offset_ptr<T>>. Таким образом,Boost.Interprocessраспределители могут быть размещены в сегменте памяти, управляемом менеджером сегмента, то есть совместно используемой памяти, картированных файлов памяти и т. д.

Сегрегированные пулы хранения просты и следуют классическому алгоритму сегрегированного хранения.

  • Бассейн распределяет куски памяти, используя функции распределения сырой памяти менеджера сегмента.
  • Кусок содержит указатель для формирования единственно связанного списка кусков. Бассейн будет содержать указатель на первый кусок.
  • Остальная часть памяти фрагмента разделена на узлы запрашиваемого размера, и никакая память не используется в качестве полезной нагрузки для каждого узла. Поскольку память свободного узла не используется, эта память используется для размещения указателя для формирования отдельно связанного списка свободных узлов. Бассейн имеет указатель на первый бесплатный узел.
  • Выделение узла — это просто получение первого бесплатного узла из списка. Если список пуст, выделяется новый кусок, связанный в списке кусков, а новые свободные узлы связаны в списке свободных узлов.
  • Deallocation возвращает узел в список свободных узлов.
  • Когда бассейн разрушается, список кусков проходит и память возвращается менеджеру сегмента.

Бассейн реализован классамиprivate_node_pool и shared_node_pool.

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

  • Вместо использования исходного распределения пул выделяетвыровненныекуски памяти с помощью менеджера сегмента. Этосущественнаяфункция, так как узел может достичь своей кусковой информации, применяя простую маску к своему адресу.
  • Куски содержат указатели для формирования двойного списка кусков и дополнительный указатель для создания отдельно связанного списка свободных узлов, размещенных на этом куске. Таким образом, в отличие от алгоритма сегрегированного хранения, бесплатный список узлов реализованна кусок.
  • Бассейн поддерживает куски в увеличивающемся порядке свободных узлов. Это улучшает локальность и минимизирует дисперсию распределения узлов по кускам, облегчая создание полностью свободных кусков.
  • Бассейн имеет указатель на кусок с минимальными (но не нулевыми) свободными узлами. Этот кусок называется «активным».
  • Выделение узла — это просто возврат первого свободного узла «активного» куска. Список кусков переупорядочен по количеству свободных узлов. При необходимости указатель на «активный» пул обновляется.
  • Если в бассейне заканчиваются узлы, выделяется новый кусок и выталкивается обратно в список кусков. При необходимости указатель на «активный» пул обновляется.
  • Deallocation возвращает узел в список свободных узлов и соответственно обновляет «активный» пул.
  • Если количество полностью свободных кусков превышает лимит, куски возвращаются менеджеру сегмента.
  • Когда бассейн разрушается, список кусков проходит и память возвращается менеджеру сегмента.

Адаптивный пул реализован классамиprivate_adaptive_node_pool и adaptive_node_pool.

Boost.Interprocessконтейнеры являются стандартными соответствующими аналогами контейнеров STL в пространстве имен<boost::interprocess>, но с этими небольшими деталями:

  • Boost.InterprocessSTL-контейнеры не предполагают, что память, выделенная с помощью распределителя, может быть размещена с другим распределителем того же типа. Они всегда сравнивают распределителей с<operator==()>, чтобы знать, возможно ли это.
  • Указатели внутренних структурУсиление.Интерпроцессконтейнеры имеют тот же тип<pointer>типа, который определяется распределителем контейнера. Это позволяет размещать контейнеры в управляемых сегментах памяти, отображаемых в разных базовых адресах.

Этот раздел пытается объяснить эксплуатационные характеристики.Boost.Interprocess, так что вы можете оптимизироватьBoost.Interprocessиспользование, если вам нужно больше производительности.

Вы можете иметь два типа исходных выделений памяти с классамиBoost.Interprocess:

  • Явный: Пользователь вызывает<allocate()>и<deallocate()>функции управляемого_shared_memory/managed_mapped_file... управляемые сегменты памяти. Этот вызов переводится на функцию<MemoryAlgorithm::allocate()>, что означает, что вам понадобится только время, которое алгоритм памяти, связанный с управляемым сегментом памяти, должен выделить данные.
  • Имплицитно: Например, вы используете<boost::interprocess::allocator<...>Boost.Interprocessконтейнерами. Этот распределитель называет ту же функцию<MemoryAlgorithm::allocate()>, что и явный метод,каждыйраз вектор/струна должен перераспределять свой буфер иликаждыйраз, когда вы вставляете объект в контейнер узла.

Если вы видите, что выделение памяти является узким местом в вашем приложении, у вас есть следующие альтернативы:

  • Если вы используете ассоциативные контейнеры с картой/набором, попробуйте использовать семейство<flat_map>вместо семейства карт, если вы в основном выполняете поиск, и вставка/удаление в основном выполняется в фазе инициализации. Накладные расходы теперь, когда упорядоченный вектор должен перераспределить свое хранилище и переместить данные. Вы также можете позвонить по методу<reserve()>этих контейнеров, если заранее знаете, сколько данных вы вставите. Однако в этих контейнерах итераторы недействительны во вставках, поэтому эта замена эффективна только в некоторых приложениях.
  • ИспользуйтеBoost.Interprocessобъединенный распределитель для контейнеров узлов, потому что объединенные распределители вызывают<allocate()>только тогда, когда у пула заканчиваются узлы. Это довольно эффективно (намного больше, чем текущий алгоритм общего назначения по умолчанию), и это может сэкономить много памяти. См.Сегрегированные распределители узлов храненияиАдаптивные распределители узловдля получения дополнительной информации.
  • Напишите свой собственный алгоритм памяти. Если у вас есть опыт работы с алгоритмами распределения памяти и вы считаете, что другой алгоритм лучше подходит для вашего приложения, вы можете указать его во всех.Boost.Interprocessуправляет сегментами памяти. См. разделНаписание нового алгоритма распределения общей памяти, чтобы знать, как это сделать. Если вы считаете, что он лучше, чем стандартный для приложений общего назначения, будьте вежливы и пожертвуйте его.Boost.Interprocess, чтобы сделать его по умолчанию!

Boost.Interprocessдопускает тот же параллелизм, что и две нити, записывающиеся в общую структуру, за исключением случаев, когда пользователь создает/исследует названные/уникальные объекты. Шаги при создании названного объекта следующие:

  • Заблокируйте рекурсивный мутекс (чтобы вы могли сделать именованные распределения внутри конструктора создаваемого объекта).
  • Попробуйте вставить указатель имени, информацию об объекте в индекс имени / объекта. Этот поиск должен гарантировать, что имя не использовалось ранее. Это достигается функцией вызова<insert()>в индексе. Поэтому время, необходимое для этого, зависит от типа индекса (упорядоченный вектор, дерево, хеш...). Это может потребовать вызова функции распределения алгоритма памяти, если индекс должен быть перераспределен, это распределитель узлов, использует объединенные распределения.
  • Выделите один буфер для хранения названия объекта, самого объекта и метаданных для уничтожения (количество объектов и т. д.).
  • Назовите строителей создаваемого объекта. Если это массив, один конструатор на элемент массива.
  • Разблокируйте рекурсивный мутекс.

Шаги при уничтожении поименованного объекта с использованием названия объекта<destroy<T>(name)>следующие:

  • Заблокируйте рекурсивный мутекс.
  • Найдите в индексе запись, связанную с этим именем. Копируйте эту информацию и стирайте ввод индекса. Это делается с использованием<find(constkey_type&)>и<erase(iterator)>членов индекса. Для этого может потребоваться переупорядочение элементов, если индекс представляет собой сбалансированное дерево, упорядоченный вектор.
  • Назовите деструктор объекта (многие, если это массив).
  • Выделите буфер памяти, содержащий имя, метаданные и сам объект, используя алгоритм распределения.
  • Разблокируйте рекурсивный мутекс.

Шаги при уничтожении поименованного объекта с помощью указателя объекта<destroy_ptr(T*ptr)>следующие:

  • Заблокируйте рекурсивный мутекс.
  • В зависимости от типа индекса это может быть разным:
    • Если индекс представляет собой индекс узла (отмеченный специализацией<boost::interprocess::is_node_index>): Возьмите итератор, хранящийся рядом с объектом, и позвоните<erase(iterator)>. Для этого может потребоваться переупорядочение элементов, если индекс представляет собой сбалансированное дерево, упорядоченный вектор.
    • Если это не индекс узла: Взять имя, хранящееся рядом с объектом, и стереть запись индекса, вызывающую 'erase(const key &). Для этого может потребоваться переупорядочение элементов, если индекс представляет собой сбалансированное дерево, упорядоченный вектор.
  • Назовите деструктор объекта (многие, если это массив).
  • Выделите буфер памяти, содержащий имя, метаданные и сам объект, используя алгоритм распределения.
  • Разблокируйте рекурсивный мутекс.

Если вы видите, что производительность недостаточно хороша, у вас есть следующие альтернативы:

  • Возможно, проблема в том, что время блокировки слишком велико и это вредит параллелизму. Постарайтесь уменьшить количество названных объектов в глобальном индексе, и если ваше приложение обслуживает несколько клиентов, попробуйте построить новый сегмент управляемой памяти для каждого из них вместо использования общего.
  • Используйте другой тип индексаBoost.Interprocess, если вы чувствуете, что по умолчанию он недостаточно быстрый. Если вы не удовлетворены, напишите свой собственный тип индекса. См.Создание пользовательских индексовдля этого.
  • Уничтожение с помощью указателя происходит по меньшей мере так же быстро, как при использовании имени объекта, и может происходить быстрее (например, в контейнерах узлов). Поэтому, если ваша проблема заключается в том, что вы делаете много названных разрушений, попробуйте использовать указатель. Если индекс является индексом узла, вы можете сэкономить некоторое время.

PrevUpHomeNext

Статья Architecture and internals раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 16. Boost.Interprocess может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Chapter 16. Boost.Interprocess ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-07-05 02:44:13/0.011147975921631/0