Совет 11. Учитывайте область применения пользовательских распределителей памяти
Совет 11. Учитывайте область применения пользовательских распределителей памяти
Итак, в результате хронометража, профилирования и всевозможных экспериментов вы пришли к выводу, что стандартный распределитель памяти STL (то есть allocator<T>) работает слишком медленно, напрасно расходует или фрагментирует память, и вы лучше справитесь с этой задачей. А может быть, allocator<T> обеспечивает безопасность в многопоточной модели, но вы планируете использовать только однопоточную модель и не желаете расходовать ресурсы на синхронизацию, которая вам не нужна. Или вы знаете, что объекты некоторых контейнеров обычно используются вместе, и хотите расположить их рядом друг с другом в специальной куче, чтобы по возможности локализовать ссылки. Или вы хотите выделить блок общей памяти и разместить в нем свои контейнеры, чтобы они могли использоваться другими процессами. Превосходно! В каждом из этих сценариев уместно воспользоваться нестандартным распределителем памяти.
Предположим, у вас имеются специальные функции для управления блоком общей памяти, написанные по образцу malloc и free:
void* mallocShared(size_t bytesNeeded);
void freeShared(void *ptr);
Требуется, чтобы память для содержимого контейнеров STL выделялась в общем блоке. Никаких проблем:
template<typename Т>
class SharedMemoryAllocator{
public:
...
pointer allocate(size_type numObjects, const void* localityHint=0)
{
return static_cast<pointer>(mal1ocShared(numObjects *szeof(T)));
}
void deallocate(pointer ptrToMemory, size_type numObjects) {
freeShared(ptrToMemory);
}
}:
За информацией о типе pointer, а также о преобразовании типа и умножении при вызове allocate обращайтесь к совету 10. Пример использования SharedMemoryAllocator:
// Вспомогательное определение типа
typedef
vector<double.SharedMemoryAllocator<double> > SharedDoubleVec:
{// Начало блока
SharedDoubleVec v;// Создать вектор, элементы которого
// находятся в общей памяти
}// Конец блока
Обратите особое внимание на формулировку комментария рядом с определением v. Вектор v использует SharedMemoryAllocator, потому память для хранения элементов v будет выделяться из общей памяти, однако сам вектор v (вместе со всеми переменными класса) почти наверняка не будет находиться в общей памяти. Вектор v — обычный стековый объект, поэтому он будет находиться в памяти, в которой исполнительная система хранит все обычные стековые объекты. Такая память почти никогда не является общей. Чтобы разместить в общей памяти как содержимое v, так и сам объект v, следует поступить примерно так:
void *pVectorMemory =// Выделить блок общей памяти,
mallocShared(sizeof(SharedOoubleVec)); // обьем которой достаточен
// для хранения объекта SharedDoubleVec
SharedDoubleVec *pv =// Использовать "new с явным
new (pVectorMemory) SharedDoubleVec; // размещением" для создания
// объекта SharedDoubleVec:
// см. далее.
// Использование объекта (через pv)
pv->~SharedDoubleVec();// Уничтожить объект в общей памяти
freeShared(pVectorMemory);// Освободить исходный блок
// общей памяти
Надеюсь, смысл происходящего достаточно ясен из комментариев. В общих чертах происходит следующее: мы выделяем бок общей памяти и конструируем в ней vector, использующий общую память для своих внутренних операций. После завершения работы с вектором мы вызываем его деструктор и освобождаем память, занимаемую вектором. Код не так уж сложен, но все-таки он не сводится к простому объявлению локальной переменной, как прежде. Если у вас нет веских причин для того, чтобы в общей памяти находился сам контейнер (а не его элементы), я рекомендую избегать четырехшагового процесса «выделение/конструирование/уничтожение/освобождение».
Несомненно, вы заметили: в приведенном фрагменте проигнорирована возможность того, что mallocShared может вернуть null. Разумеется, в окончательной версии следовало бы учесть такую возможность. Кроме того, конструирование vector в общей памяти производится конструкцией «new с явным размещением», описанной в любом учебнике по С++.
Рассмотрим другой пример использования распределителей памяти. Предположим, у нас имеются две кучи, представленные классами Heap1 и Неар2. Каждый из этих классов содержит статические функции для выделения и освобождения памяти:
class Heap1 {
public:
static void* alloc(size t numBytes, const void* memoryBlockToBeNear);
static void dealloc(void *ptr);
};
class Heap2 {...}; // Тот же интерфейс alloc/dealloc
Далее предположим, что вы хотите разместить содержимое контейнеров STL в заданных кучах. Сначала следует написать распределитель, способный использовать классы Heap1 и Неар2 при управлении памятью:
template<typename T,typename Heap>
SpecificHeapAllocator{
public:
...
pointer allocate(size_type numObjects,const void *localityHint=0) {
return static_cast<pointer> (Heap::alloc(numObjects*sizeof(T), localityHint)):
}
void deallocate(pointer ptrToMemory,size_type numObjects) {
Heap::dealloc(ptrToMemory);
}
...
};
Затем SpecialHeapAllocator группирует элементы контейнеров:
vector<int.SpecificHeapAllocator<int,Heap1> > v; // Разместить элементы
set<int,SpecificHeapAllocator<int,Heap1> > s: // v и s в Heapl
list<Widget,
SpecificHeapAllocator<Widget,Heap2> > L; // Разместить элементы
map<int.string.less<int>,// L и m в Heap2
SpecificHeapAllocator<pair<const int,string>. Heap2> > m;
В приведенном примере очень важно, чтобы Heap1 и Неар2 были типами, а не объектами. В STL предусмотрен синтаксис инициализации разных контейнеров STL разными объектами распределителей одного типа, но я не буду его приводить. Дело в том, что если бы Heap1 и Неар2 были бы объектами вместо типов, это привело бы к нарушению ограничения эквивалентности, подробно описанного в совете 10.
Как показывают приведенные примеры, распределители приносят пользу во многих ситуациях. При соблюдении ограничения об эквивалентности однотипных распределителей у вас не будет проблем с применением нестандартных распределителей для управления памятью, группировки, а также использования общей памяти и других специализированных пулов.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКЧитайте также
Совет 10. Помните о правилах и ограничениях распределителей памяти
Совет 10. Помните о правилах и ограничениях распределителей памяти Распределители памяти первоначально разрабатывались как абстракция для моделей памяти, позволяющих разработчикам библиотек игнорировать различия между near- и far-указателями в некоторых 16-разрядных
Области применения. NET
Области применения. NET Как и было заявлено Microsoft с самого начала обнародования сведений о. NET, этот набор технологий в первую очередь адресован программистам, которые работают с веб-приложениями, и предназначен для создания расширяемых распределенных приложений, которые,
Требования распределителей (Allocator requirements)
Требования распределителей (Allocator requirements) В следующей таблице мы предполагаем, что X - класс распределителей для объектов типа T, a - значение X, n имеет тип X::size_type, p имеет тип X::pointer, r имеет тип X::reference и s имеет тип X::const_reference.Все операции c распределителями, как ожидается,
16.3. Область применения
16.3. Область применения Как один из вариантов грамотного применения перенаправления ввода/вывода, можно назвать разбор и "сшивание" вывода от команд (см. Пример 11-6). Это позволяет создавать файлы отчетов и журналов регистрации
1 Область применения
1 Область применения Настоящий стандарт применяется для пакетов программ. Например, для текстовых процессоров, электронных таблиц, программ баз данных, графических пакетов, программ, реализующих технические и научные функции, и для сервисных программ (утилит).Стандарт
1 ОБЛАСТЬ ПРИМЕНЕНИЯ
1 ОБЛАСТЬ ПРИМЕНЕНИЯ Настоящий стандарт определяет шесть характеристик, которые с минимальным дублированием описывают качество программного обеспечения. Данные характеристики образуют основу для дальнейшего уточнения и описания качества программного обеспечения.
1 ОБЛАСТЬ ПРИМЕНЕНИЯ
1 ОБЛАСТЬ ПРИМЕНЕНИЯ Данный стандарт представляет собой руководство по документированию программного обеспечения для тех руководителей, которые отвечают за производство программного обеспечения или программной продукции. Руководство предназначено для помощи
Область применения проекта
Область применения проекта Этот раздел описывает общие параметры проекта, включая время, необходимое для проектирования, количество потенциальных пользователей и основные приложения, поддерживаемые PKI. На основании этой информации поставщик может подготовить более