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

Pool in More Depth

Boost , Boost.Pool , Introduction and Overview

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

Динамическое распределение памяти является фундаментальной частью большинства компьютерных систем с 1960 года.1

Каждый использует динамическое распределение памяти. Если вы когда-либо называли malloc или new, то вы использовали динамическое распределение памяти. Большинство программистов склонны рассматривать кучу как& #8220;магический мешок& #8221;: Мы просим его о памяти, и он волшебным образом создает что-то для нас. Иногда мы сталкиваемся с проблемами, потому что куча не волшебная.

Куча ограничена. Даже в больших системах (т.е. не встроенных) с огромным количеством виртуальной памяти есть предел. Все знают о физическом пределе, но есть более тонкий, «виртуальный» предел, при котором ваша программа (или вся система) замедляется из-за использования виртуальной памяти. Этот виртуальный предел намного ближе к вашей программе, чем физический, особенно если вы работаете на многозадачной системе. Поэтому при работе на большой системе считаетсянеплохозаставить вашу программу использовать как можно меньше ресурсов и выпустить их как можно скорее. При использовании встроенной системы программисты обычно не теряют память.

Груда сложная. Он должен удовлетворять любой тип запроса памяти для любого размера и делать это быстро. Общие подходы к управлению памятью связаны с разделением памяти на части и упорядочением их по размеру в виде дерева или структуры списка. Добавьте другие факторы, такие как местность и оценка жизни, и кучи быстро становятся очень сложными. На самом деле, это настолько сложно, что не существует известногоидеальногоответа на вопрос о том, как сделать динамическое распределение памяти. Диаграммы ниже иллюстрируют, как работают наиболее распространенные менеджеры памяти: для каждого фрагмента памяти он использует часть этой памяти для поддержания своей внутренней структуры дерева или списка. Даже если какой-то фрагмент выводится в программу, менеджер памяти долженсохранитьнекоторую информацию в ней — обычно только ее размер. Затем, когда блок свободен, менеджер памяти может легко определить, насколько он велик.

Dynamic memory allocation is often inefficient

Из-за усложнения динамического распределения памяти, он часто неэффективен с точки зрения времени и / или пространства. Большинство алгоритмов распределения памяти хранят некоторую форму информации с каждым блоком памяти, размером блока или некоторой реляционной информацией, такой как его положение во внутреннем дереве или структуре списка. Для такихполей заголовкаобычно используется одно машинное слово в блоке, который используется программой. Очевидным недостатком является то, что небольшие объекты динамически распределяются. Например, если инты были динамически распределены, то алгоритм автоматически зарезервирует пространство для полей заголовка, и мы в конечном итоге потеряем 50% памяти. Конечно, это наихудший сценарий. Однако более современные программы используют небольшие объекты на куче, что делает эту проблему все более очевидной. Уилсон и др. АЛ. утверждают, что средняя сумма накладных расходов на память составляет от десяти до двадцати процентов2. Накладные расходы на память будут расти, поскольку все больше программ используют более мелкие объекты. Именно накладные расходы на память приближают программы к виртуальному пределу.

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

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

Простое сегрегированное хранилище является основной идеей библиотеки Boost Pool. Simple Segregated Storage — самый простой и, вероятно, самый быстрый алгоритм распределения памяти. Он начинается с разделения блока памяти на куски фиксированного размера. То, откуда происходит блок, не важно до момента реализации. Бассейн — это объект, который использует простое сегрегированное хранилище таким образом. Для иллюстрации:

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

Простая сегрегация Хранение работает путем чередования свободного списка в неиспользованных кусках. Например:

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

Простое сегрегированное хранение также очень быстрое. В простейшем случае выделение памяти — это просто удаление первой части из свободного списка, операция O(1). В случае, когда свободный список пуст, другой блок может быть приобретен и разделен, что приведет к амортизированному времени O(1). Распределение памяти может быть таким же простым, как добавление этого фрагмента в переднюю часть свободного списка, операция O(1). Однако для более сложного использования Simple Segregated Storage может потребоваться отсортированный бесплатный список, который делает распределение сделок O(N).

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

Terminology

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

Overview

Каждый пул имеет один бесплатный список, который может распространяться на несколько блоков памяти. Таким образом, Бассейн также имеет связанный список выделенных блоков памяти. Каждый блок памяти по умолчанию выделяется с помощью<new[]>, и все блоки памяти освобождаются от разрушения. Именно использование<new[]>позволяет нам гарантировать выравнивание.

Proof of Concept: Guaranteeing Alignment

Каждый блок памяти выделяется как тип POD (в частности, массив символов) через<operator new[]>. Пусть<POD_size>будет число выделенных знаков.

Predicate 1: Arrays may not have padding

Это следует из следующей цитаты:

[5.3.3/2] (Выражения::Унитарные выражения::Sizeof)... При применении к массиву результатом является общее количество байтов в массиве. Это означает, что размер массива n элементов в n раз больше размера элемента.

Поэтому массивы не могут содержать набивку, хотя элементы внутри массивов могут содержать набивку.

Predicate 2: Any block of memory allocated as an array of characters through operator new[] (hereafter referred to as the block) is properly aligned for any object of that size or smaller

Это следует из следующего:

  • [3.7.3.1/2] (Основные понятия: Продолжительность хранения:: Динамическая продолжительность хранения:: Функции распределения)"... Возвращаемый указатель должен быть соответствующим образом выровнен таким образом, чтобы его можно было преобразовать в указатель любого полного типа объекта и затем использовать для доступа к объекту или массиву в выделенном хранилище..."
  • [5.3.4/10] (Выражения::Единые выражения::Новые)"... Для массивов char и unsigned char разница между результатом нового выражения и адресом, возвращаемым функцией распределения, должна быть интегральной кратной наиболее строгому требованию (3.9) выравнивания любого типа объекта, размер которого не превышает размера создаваемого массива. [Примечание: Поскольку предполагается, что функции распределения возвращают указатели в хранилище, которое надлежащим образом выровнено для объектов любого типа, это ограничение на накладные расходы на распределение массивов допускает общую идиому распределения массивов символов, в которые впоследствии будут помещены объекты других типов».
Consider: imaginary object type Element of a size which is a multiple of some actual object size; assume sizeof(Element) > POD_size

Обратите внимание, что объект такого размера может существовать. Одним из объектов такого размера является массив «фактических» объектов.

Обратите внимание, что блок правильно выровнен для элемента. Это следует из предикат 2.

Corollary 1: The block is properly aligned for an array of Elements

Это следует из предикатов 1 и 2 и следующей цитаты:

[3.9/9] (Основные понятия::Типы)«Тип объекта — это (возможно, cv-квалифицированный) тип, который не является типом функции, не является эталонным типом и не является пустотным типом».

(В частности, типы массивов являются типами объектов.)

Corollary 2: For any pointer p and integer i, if p is properly aligned for the type it points to, then p + i (when well-defined) is properly aligned for that type; in other words, if an array is properly aligned, then each element in that array is properly aligned

Нет цитат из Стандарта, чтобы непосредственно поддержать этот аргумент, но он соответствует общей концепции значения «выравнивания».

Следует отметить, что условия для<p +i>, которые должны быть четко определены, изложены в [5.7/5]. Мы не цитируем это здесь, но только заметим, что это четко определено, если<p>и<p+ i>оба указывают на один и тот же массив.

Let: sizeof(Element) be the least common multiple of sizes of several actual objects (T1, T2, T3, ...)
Let: block be a pointer to the memory block, pe be (Element *) block, and pn be (Tn *) block
Corollary 3: For each integer i, such that pe + i is well-defined, then for each n, there exists some integer jn such that pn + jn is well-defined and refers to the same memory address as pe + i

Это следует естественно, поскольку блок памяти представляет собой массив Элементов, и для каждого n,<sizeof(Element)%sizeof(Tn) ==0;>таким образом, граница каждого элемента в массиве Элементов также является границей каждого элемента в каждом массиве Tn.

Theorem: For each integer i, such that pe + i is well-defined, that address (pe + i) is properly aligned for each type Tn

Так как<pe+ i>хорошо определено, то по следствию 3<pn+ jn>хорошо определено. Он правильно выровнен из Предиката 2 и Следствий 1 и 2.

Use of the Theorem

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

  • Запрашиваемый размер объекта<requested_size>; это размер кусков, запрашиваемых пользователем
  • <void*>(указатель на пустоту); это потому, что мы переплетаем наш свободный список через куски.
  • <size_type>Это потому, что мы сохраняем размер следующего блока в каждом блоке памяти.

Каждый блок также содержит указатель на следующий блок; но он хранится как указатель на пустоту и отбрасывается при необходимости, чтобы упростить требования к выравниванию для трех типов выше.

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

A Look at the Memory Block

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

Каждая из этих секций может содержать прокладку по мере необходимости, чтобы гарантировать выравнивание для каждой из следующих секций. Размер первой секции<number_of_chunks* lcm(requested_size, sizeof(void*),sizeof(size_type));>, размер второй секции<lcm(sizeof(void*), sizeof(size_type);>и размер третьей секции<sizeof(size_type)>.

Вот пример блока памяти, где<requested_size ==sizeof(void*) ==sizeof(size_type)==4>:

Чтобы показать визуальный пример возможной прокладки, вот пример блока памяти, где<requested_size==8and sizeof(void*)==sizeof(size_type)==4>

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

Используя массив аргументов, аналогичных приведенным выше, мы можем перевести любой запрос на непрерывную память для<n>объектов<requested_size>в запрос на m смежных кусков.<m>— это просто<ceil(n*requested_size/ alloc_size)>, где<alloc_size>— фактический размер кусков.

Для иллюстрации:

Вот пример блока памяти, где<requested_size ==1>и<sizeof(void*)==sizeof(size_type)==4>:

Затем, когда пользователь распределяет смежную память, мы можем снова разделить ее на куски.

Отметим, что реализация, предусмотренная для выделения смежных кусков, использует линейный вместо квадратичного алгоритм. Это означает, что он не может найти смежные свободные куски, если свободный список не упорядочен. Таким образом, рекомендуется всегда использовать упорядоченный бесплатный список при работе с смежным распределением кусков. (В приведенном выше примере, если бы Chunk 1 указал на Chunk 3 указал на Chunk 2 указал на Chunk 4, вместо того, чтобы быть в порядке, алгоритм непрерывного распределения не смог бы найти какой-либо из смежных кусков).

Introduction

<simple_segregated_storage.hpp>предоставляет класс шаблонов simple_segregated_storage, который контролирует доступ к свободному списку фрагментов памяти.

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

Synopsis
template <typename SizeType = std::size_t>
class simple_segregated_storage
{
  private:
    simple_segregated_storage(const simple_segregated_storage &);
    void operator=(const simple_segregated_storage &);
  public:
    typedef SizeType size_type;
    simple_segregated_storage();
    ~simple_segregated_storage();
    static void * segregate(void * block,
        size_type nsz, size_type npartition_sz,
        void * end = 0);
    void add_block(void * block,
        size_type nsz, size_type npartition_sz);
    void add_ordered_block(void * block,
        size_type nsz, size_type npartition_sz);
    bool empty() const;
    void * malloc();
    void free(void * chunk);
    void ordered_free(void * chunk);
    void * malloc_n(size_type n, size_type partition_sz);
    void free_n(void * chunks, size_type n,
        size_type partition_sz);
    void ordered_free_n(void * chunks, size_type n,
        size_type partition_sz);
};
Semantics

Объект типа<simple_segregated_storage<SizeType>>пуст, если его свободный список пуст. Если он не пустой, то заказывается, если заказывается его бесплатный список. Свободный список упорядочен, если повторные вызовы<malloc()>приведут к постоянно увеличивающейся последовательности значений, как определено<std::less<void*>>. Функция-член сохраняет порядок, если свободный список сохраняет свою ориентацию порядка (то есть упорядоченный свободный список все еще упорядочен после вызова функции-члена).

Table 1. Symbol Table

символ

значение

Магазин

simple_segregated_storage

t

стоимость магазина типа

u

значение типа const Store

блок, кусок, конец

значения типа void *

Раздел_sz, sz, n

значения типа Store::size_type


Table 2. Template Parameters

Параметр

по умолчанию

Требования

Тип размера

std::size_t

Неподписанный интегральный тип


Table 3. Typedefs

символ

Тип

size_type

Тип размера


Table 4. Constructors, Destructors, and State

выражение

Тип возврата

Пост-состояние

Заметки

Магазин()

Не используется

пустой

Строительство нового магазина

(&t)->~Store()

Не используется

Разрушает магазин

U.empty()

bool

Returns true if u is empty. Order-preserving.


Table 5. Segregation

выражение

Тип возврата

Предварительное состояние

Пост-состояние

Семантическая эквивалентность

Заметки

Магазин::сегрегация (блок, sz, раздел_sz, конец)

void

partition_sz >= sizeof(void *) partition_sz = sizeof(void *) * i, для некоторого целого числа i sz >= partition_sz block правильно выровнен для массива объектов размера partition_sz block правильно выровнен для массива пустоты *

Переносит свободный список через блок памяти, указанный блоком размера sz байтов, разделяя его на как можно больше кусков размером с partition_sz. Последний кусок устанавливается, чтобы указать на конец, и указатель на первый член возвращается (это всегда равно блоку). Этот переплетенный бесплатный список заказывается. O(sz).

Магазин::segregate(блок, sz, partition_sz)

void

Same as above

Магазин::segregate(блок, sz, partition_sz, 0)

t.add_block(block, sz, partition_sz)

пустота

Same as above

!t.empty()

Разделяет блок памяти, указанный блоком размера sz байтов, на фрагменты размером с partition_sz и добавляет этот бесплатный список к своему собственному. Если t было пусто до этого звонка, то оно заказывается после этого звонка. O(sz).

t.add_ordered_block(block, sz, partition_sz)

пустота

Same as above

!t.empty()

Разделяет блок памяти, указанный блоком размера sz байтов, на фрагменты размером с partition_sz и объединяет этот свободный список в свой собственный. Сохранение порядка. O(sz).


Table 6. Allocation and Deallocation

выражение

Тип возврата

Предварительное состояние

Пост-состояние

Семантическая эквивалентность

Заметки

t.malloc

void

!t.empty()

Берет первый доступный кусок из бесплатного списка и возвращает его. Сохранение порядка. О(1).

t.free

пустота

chunk was previously returned from a call to t.malloc()

!t.empty()

Места в свободном списке. Обратите внимание, что кусочек не может быть 0. O(1).

t.ordered_free(chunk)

пустота

Same as above

!t.empty()

Места в свободном списке. Обратите внимание, что кусочек может не быть 0. Сохранение порядка. O(N) относительно размера бесплатного списка.

t.malloc_n(n, partition_sz)

void

Попытки найти прилежащую последовательность n кусков размером с z. Если они найдены, удалите их все из бесплатного списка и верните указатель на первый. Если не найдено, возвращается 0. Настоятельно рекомендуется (но не обязательно) упорядочить свободный список, так как этот алгоритм не сможет найти непрерывную последовательность, если она не является смежным в свободном списке. Сохранение порядка. O(N) относительно размера бесплатного списка.

t.free_n(chunk, n, partition_sz)

пустота

chunk was previously returned from a call to t.malloc_n(n, partition_sz)

!t.empty()

t.add_block(chunk, n * partition_sz, partition_sz)

Предполагает, что кусочек на самом деле относится к блоку кусков, охватывающих байты n * partition_sz; разделяет и добавляет в этот блок. Обратите внимание, что кусочек может не быть 0. O(n).

t.ordered_free_n(chunk, n, partition_sz)

пустота

Как и выше

Как и выше

t.add_ordered_block(chunk, n * partition_sz, partition_sz)

То же, что и выше, за исключением того, что он сливается в свободном списке. Сохранение порядка. O(N + n) где N - размер свободного списка.


Объекты пула должны запрашивать блоки памяти из системы, которые пул затем разделяет на куски, чтобы выделить пользователю. Указывая параметр шаблона UserAllocator для различных интерфейсов Pool, пользователи могут контролировать распределение блоков системной памяти.

В следующей таблицеUserAllocatorявляется типом User Allocator,блокявляется значением типа char *, аnявляется значением типа. UserAllocator::size_type

Table 7. UserAllocator Requirements

выражение

Результат

Описание

UserAllocator::size_type

An unsigned integral type that can represent the size of the largest object to be allocated.

UserAllocator::difference_type

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

UserAllocator::malloc(n)

char *

Попытки выделить из системы n байт. Возвращает 0, если вне памяти.

UserAllocator::free(block)

пустота

блок должен быть ранее возвращен с вызова в UserAllocator::malloc.


В этой библиотеке представлены два класса UserAllocator:<default_user_allocator_new_delete>и<default_user_allocator_malloc_free>. Значение по умолчанию для параметра шаблона UserAllocator всегда<default_user_allocator_new_delete>.


PrevUpHomeNext

Статья Pool in More Depth раздела Boost.Pool Introduction and Overview может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Introduction and Overview ::


реклама


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

Время компиляции файла: 2024-08-30 11:47:00
2025-05-20 07:36:30/0.013614892959595/0