А. Александреску - Современное проектирование на C++ (1119444), страница 26
Текст из файла (страница 26)
При поступлении нового запроса обьект класса р1хебд11осатог сначала просматривает кэш. Если он не пуст, объект извлекает из каша последний доступный указатель и немедленно возвращает его обратно. Это очень быст1юя операция. Только когда кэш опустошен, объект класса гзхедд11осатог вынужден выполнить стандартную процедуру распределения памяти для объекта класса сйцпк. Эта мнопюбешаюшая стратегия в некоюрых достаточно распространенных случаях работает плохо. При распределении памяти для небольших объектов возможны такие ситуации. ° Распределение ламяти для большого массива данных. В памяти одновременно размещается большое количество мелких объектов.
Зто происходит, например, при инициализации набора указателей на маленькие объекты. ° Удаление в ~пои же порядке. Большое количество мелких объектов удаляется в том же порядке, в котором они были размещены в памяти. Это происходит при разрушении большинства контейнеров из стандартной библиотеки шаблонов БТУ. ° Удаление в обратном порядке. Большое количество небольших объектов удаляется в порядке, обратном порядку их размещения в памяти. Для программ, напи- ' Стандарт языка С++ не определяет порялок уничтожения объектов, содержащихся в стандартном контейнере. Следовательно, каждый программист, занимающийся реализацией языка, может выбирать этот порядок по своему усмотрению.
Обычно контейнеры разрушаются с помощью простого прямого повторения. Однако в некоторых реализациях разработчики сочли "более естественным" уничтожать объекты в обратном порядке. В обоснование этого приводится факт, что в языке С++ объекты разрушаются именно в обратном порялкс. 108 Часть!. Методы санных на языке С++, это вполне естественная ситуация при вызове функций, работающих с небольшими объектами. Аргументы функции и временные переменные стека соответствуют именно этому случаю. ° Произвольное размещение и удаление. Объекты создаются и удаляются без определенного порялка.
Это происходит, когда при выполнении программы время от времени возникает необходимость в небольших объектах. Стратегия кэширования очень хорошо соответствует ситуации, при каждой объекты размещаются и удаляются в произвольном порядке. Однако при размещении и удалении большого массива данных кэширование оказывается бесполезным. Хуже того, оно замедляет процесс удаления объектов, поскольку каждый раз тратит дополнительное время на очистку буфера.з Лучше всего применять стратегию удаления обьектов, совпадающую со стратегией их размещения в памяти.
Переменная-член г1хебд11осатог::деа11осСпцп1с указьпюет на последний объект класса спцпк, который бььт использован для удаления из памяти. Как только поступает запрос на удаление, эта переменная проверяется первой. Затем, если она ссылается на неверный объект типа спцпк, функция пеа11осате начинает линейный поиск, обнаруживает требуемый объект и обновляет переменную деа11осСЬцп~ .
Есть два важных приема, позволяющих ускорить работу функции пеа11осате в указанных выше ситуациях. Во-первых, функция пеа11осате выполняет поиск подходящего объекта класса спцпк, начиная с окрестности указателя беа!1осСпцп1с . Это значит, что вектор спцпкз просматривается, начиная с переменной деа11осспцпк, а следующими проверяются итераторы, находящиеся выше и ниже. Это значительно повышает скорость улаления болыцого массива данных при любом порядке удаления (прямом или обратном).
Во время размещения большого массива данных функция А11осате добавляет объекты класса спцпк по порядку. Во время удаления либо указатель деа11осспцпк сразу попадает в точку, либо правильный объект будет обнаружен на следующем шаге. Во втором трюке следует избегать крайних вариантов. Допустим, оба указателя а1- 1осспцп1с и деа11осспцп1с ссылаются на последний объект в векторе, и свободного места в этом множестве не осталось. Представьте, что произойлет, если будет выполняться следующий код.
бог (...) ( // некоторые интеллектуальные указатели используют свой // механизм распределения памяти для небольших объектов // (глава 7) быагтгтг р; используется указатель р ... При кажлом прохоле цикла создается и уничтожается объект класса 5вагтятг. При создании объекта, поскольку памяти больше нет, функция гбхедд11осатог:гд11осате создает новый объект класса спцпк и заносит его в вектор спипкз . При разрушении объекта функция гбхеда11осатог::пеа11осате обнаруживает пус- З Мне ие удатось создать разумную схему кэширования, которая одинаково хорошо работала бы при удалении объектов в прямом и обратном порядке.
Кэширование наносит вред либо одному, либо другому процессу. Поскольку обе ситуации часто встречаются в реальных программах, кэширование лучше ие применять. 109 Глава 4. Размещение в памяти небольших объвктов той блок и освобождает его. Эти затратные процедуры повторяются на каждой итерации цикла бог. Такая неэффективная работа недопустима. Следовательно, во время удаления объект класса сЬипк должен освобождаться, только если есть два пустых объекта этого класса. При наличии только одного пустого участка он быстро перемешается в конец вектора сЬцпкз .
Таким образом мы избегаем выполнения затратных операций чессог<сЬцпк>::егазе, всегда удаляя лишь последний элемент. Разумеется, могут возникнуть ситуации, при которых такой подход неприменим. При размещении в цикле вектора объектов класса 5вагСРСг определенного размера может возникнуть та же проблема. Однако такие ситуации встречаются все реже и реже, Кроме того, как указывалось во введении, любой механизм распределения памяти в определенных ситуациях может оказаться хуже остальных. Выбранная стратегия удаления объектов хорошо соответствует произвольному порядку их создания.
Даже если данные размещались без определенного порядка, программы стремятся достичь определенной локальности (1оса!йу), каждый раз обращаясь к небольшой порции данных. Указатели а11оссЬцпх и деа11оссЬцпк в этих ситуациях работают безукоризненно, поскольку при последующем размещении и удалении объектов эти указатели действуют как кэш.
Итак, у нас есть класс г(хедд11осасог, достаточно эффективно удовлетворяющий запросы на распределение памяти фиксированного размера как с точки зрения затрачиваемого времени, так и с точки зрения использования памяти. Класс г(хедд11осасог оптимизирован лля работы с небольшими объектами. 4.6. Класс ЗтаПОЬ1АПоса1ог Третий уровень нашей архитектуры механизма распределения памяти состоит из класса 5ва11оЬ)л11осасог, позволяющего размешать в памяти объекты любого размера, объединяя в одно целое несколько объектов класса г(хедд11осасог.
Получив запрос на распределение памяти, объект класса 5йа11оЬ)д11осасог либо переадресует его наиболее подходящему объекту класса я(хедд11осасог, либо передаст его оператору::орегасог пеп. Ниже приведено краткое описание класса 5еа11оЬ)д11осасог. Объяснения даются после кода. с1аав 5ва110Ь)л11осасог ( рцЬ1(с: 5па110Ь)д11осасог( зсд::ззсе с сЬцпк5(ге, зсд::з(се с еахоЬ)есс5(ге); чо(6 л11осасе(зсд::51ге с пиавусез); чодд цеа11осасе(чозд* р, зсд::51ге с з(хе); рг1часе: зсд;:чессог<г1хедд11осасог> роо1 Конструктор получает два параметра, определяющих конфигурацию объекта класса 5ва110Ь)д11осасог.
Параметр 5(ае залает размер участка памяти, принятый по умолчанию (длина каждого объекта класса СЬцпк, выраженная в байтах), а параметр вахоЬ)есс51ае задает максимально возможный размер объектов, которые еще ма~ус Часть). Методы считаться "небольшими". Объект класса Бяа110Ьзл11осатог переадресовывает запросы на выделение блоков, размер которых превышает величину яахоЬ) ествзхе, непосредственно оператору::орегатог пен. Довольно необычно, что функция пеа11осате получает размер объекта, подлежащего улалению, в качестве аргумента. Дело в том, что это позволяет ускорить процесс освобождения памяти. В противном случае функция биа11оЬ)д11осатог:: пеа11осате должна была бы искать среди всех объектов класса гз хеда11осатог тот, которому принадлежит данный указатель. Это слишком затратная процедура, поэтому объекту класса зяа11оЬ)л11осатог нужно передавать размер удаляемого блока.
Как мы увидим в следующей главе, эта задача изящно решается самим компилятором. Какое отображение существует между размером блока в объекте Рз'хебл11осатог и вектором роо1 ? Иными словами, как по заданному размеру отыскать ссютветствуюший объект класса гзхедд11осатог, который выполняет распределение памяти и ее освобождение? Простая и эффективная идея заключается в том, чтобы элемент роо1 [з 3 обрабатывал объекты размера з. Вектор роо1 инициализируется с размером нахоЬ)естбзае, а затем инициализируются все объекты класса г(хеда11осатог, содержащиеся в нем. Если поступает запрос на выделение памяти размером пцпвутез, объект кдасса биа11оЬ)л11осатог передает его либо элементу роо1 [пцивутез) (эта операция имеет постоянное время выполнения), либо оператору:: орегатог пеп.
Однако это решение не настолько удачное, как кажется на первый взгляд. В конкретных ситуациях нам могут понадобиться распределители памяти только одного определенного размера, например 4 байт, и никакие другие объекты не нужны. В этом случае придется распределять память для 64 и большего количества элементов вектора роо1, хотя на самом деле используются только два элемента. Выравнивание размеров и заполнение пустот еше больше снижают эффективное использование памяти.
Например, многие компиляторы дополняют все типы, определенные пользователем, до размера, кратного заданному числу (2, 4 и т.д.). Например, если компилятор дополняет все структуры до размера, кратного 4, то используется только 25% элементов вектора роо1, остальные представляют собой балласт. Намного лучше пожертвовать скоростью просмотра, но сэкономить память'. Мы будем хранить объекты класса я1хедл11осасог только для тех размеров памяти, которые требуются хотя бы олин раз. Таким образом, вектор роо1 может содержать много объектов разного размера, не слишком увеличиваясь.
Для того чтобы ускорить просмотр, вектор роо1 хранится отсортированным по размерам блоков. Кроме того, ускорить просмотр можно с помощью стратегии, которая уже применялась в классе Рз'хебл11осатог. Объект класса вна11оЬ)д11осатог хранит указатель на послелний объект класса я!хебд11осатог, использованный при освобождении памяти. Ниже приведен полный список переменных-членов класса бва11л11осатог. с1ааа биа110Ь)л11осатог ргз'вате: зтб::честог<гзхедд11осатог> роо1 г1хедл11осатог* рьазтл11ос ; з Факгически в современных системах экономил памяти валет к ускорению работы.