Автор работы: Пользователь скрыл имя, 26 Октября 2010 в 22:57, Не определен
Реферат
Динамически распределяемая
память
Третий, и последний,
мехянизм управления памятью — динамически
распределяемые области памяти, или
кучи (heaps). Они весьма удобны при
создании множества не больших блоков
данных. Например, связанными списками
и деревьями проще мани пулировать,
используя именно кучи, а не виртуальную
память (глава 15) или файлы, проецируемые
в память (глава 17) Преимущество динамически
распределяемой па мяти в том, что она
позволяет Вам игнорировать гранулярность
выделения памяти и размер страниц и сосредоточиться
непосредственно на своей задаче. А недостаток
— выделение и освобождение блоков памяти
проходит мсдлсннсс, чсм при использова
нии других механизмов, и, кроме того, Вы
теряете прямой контроль над передачей
физической памяти и ее возвратом системе.
Куча — это
регион зарезервированного адресного
пространства. Первоначально большей
его части физическая память не передается.
По мере того, как программа занимает
эту область под данные, специальный
диспетчер, управляющий кучами (heap manager),
постранично передаст ей физическую память
(из страничного файла). А при освобождении
блоков в куче диспетчер возвращает системе
соответствующие стра ницы физической
памяти.
Microsoft не документирует правила, по которым диспетчер передает или отбира ет физическую память. Эти правила различны в Windows 98 и Windows 2000. Могу сказать Вам лишь следующее; Windows. 98 больше озабочена эффективностью исполь зования памяти и поэтому старается как можно быстрее отобрать у куч физическую память. Однако Windows 2000 нацелена главным образом на максимальное быстро действие, в связи с чем возвращает физическую память в страничный файл, только если страницы не используются в течение определенного времени. Microsoft посто янно проводит стрессовое тестирование своих операционных систем и прогоняет разные сценарии, чтобы определить, какие правила в большинстве случаев работают лучше. Их приходится менять по мере появления как нового программного обеспе чения, так и оборудования. Если эти правила важны Вашим программам, использо вать динамически распределяемую память пе стоит — работайте с функциями вирту альной памяти (т. e. VirtualAlloc и VirtualFree), и тогда Вы сможете сами контролиро вать эти правила.
Стандартная куча
процесса
При инициализации
процесса система создает в его адресном
пространстве стандарт ную кучу (process's
default heap) Ее размер по умолчанию — 1 Мб Но
система позво ляет увеличивать этот размер,
для чего надо указать компоновщику при
сборке про
граммы ключ
/HEAP (Однако при сборке DLL этим ключом
пользоваться нельзя, так какдля DLL куча
не создается.)
/HEAP:reserve[,commit]
Стандартная куча
процесса необходима многим Windows-функциям.
Например, функции ядра Windows 2000 выполняют
все операции с использованием Unicode
символов и строк Если вызвать ANSI-версию
какой-нибудь Windows-функции, ей придется,
преобразовав строки ил ANSI в Unicode, вызывать
свою Unicode-версию. Для преобразования строк
ANSI-функции нужно выделить блок памяти,
в котором она размещает Unicode-версию строки.
Этот блок памяти заимствуется из стандартной
кучи вызывающего процесса. Есть и другие
функции, использующие временные бло ки
памяти, которые тоже выделяются из стандартной
кучи процесса Из нее же чер пают себе
память и функции 16~разрядной Windows, управляющие
кучами (LocalAlloc и GlobalAlloc).
Поскольку стандартную
кучу процесса используют многие Windows-функции,
а потоки Вашего приложения могут
одновременно вызвать массу таких
функций, дос туп к этой куче разрешается
только по очереди. Иными словами, система
гарантиру ет, что в каждый момент времени
только один поток сможет выделить или
освобо дить блок памяти в этой куче. Если
же два потока попытаются выделить в ней
блоки памяти одновременно, второй поток
будет ждать, пока первый поток не выделит
свой блок. Принцин последовательного
доступа потоков к кучс немного снижает
произ водительность многопоточной программы.
Если в программе всего один поток, для
быстрейшего доступа к кучс нужно создать
отдельную кучу и нс использовать стан
дартную. Но Windows-функциям этого, увы, не
прикажешь — они работают с кучей только
последнего типа.
Как я уже
говорил, куч у одного процесса может
быть несколько. Они создаются и
разрушаются в период его существования.
Но стандартная куча процесса создается
в начале его исполнения и автоматически
уничтожается по его завершении — сами
уничтожить ее Вы нс можете. Каждую кучу
идентифицирует своЙ описатель, и все
Windows-функции, которые выделяют и освобождают
блоки в ее пределах, требуют передавать
им этот описатель как параметр.
Описатель стандартной
кучи процесса возвращает функция GеtProcessHeap.
HANDLE GelProcessHeap();
Дополнительные
кучи в процессе
В адресном пространстве процесса допускается создание дополнительных куч. Для чего они нужны? Тому может быть несколько причин:
защита компонентов;
более эффективное управление памятью;
локальный доступ;
исключение издержек, связанных с синхронизацией потоков;
быстрое освобождение
всей памяти в куче.
Рассмотрим эти причины подробнее.
Защита компонентов
Допустим, программа
должна обрабатывать два компонента- связанный
список струк тур NODE и двоичное дсрсво
структур BRANCH. Представим также, что у
Вас есть два
файла исходного
кода: LnkLst.cpp, содержащий функции для
обработки связанного списка, и BinTree.cpp
с функциями для обработки двоичного
дерева
Если структуры NODE и BRANCH хранятся в одной куче, то она может выглядеть примерно так, как показано на рис. 18-1.
Рис. 18-1. Единая
куча, в которой размещены структуры
NODE и BRANCH
Теперь предположим,
что в коде, обрабатывающем связанный
список, «сидит жу чок", который приводит
к случайной перезаписи 8 байтов после
NODE 1 А это в свою очередь влечет порчу
данных в BRANCH 3 Впоследствии, когда код
из файла Bin Tree.cpp пьтается «пройти" по
двоичному дереву, происходит сбой из-за
того, что часть данных в памяти испорчена.
Можно подумать, что ошибка возникает
из-за «жуч ка» в коде двоичного дерева,
тогда как на самом деле он — в коде связанного
списка. А поскольку разные типы объектов
смешаны в одну кучу (в прямом и переносном
смысле), то отловить «жучков» в коде становится
гораздо труднее.
Создав же две
отдельные кучи — одну для NODE, другую
для BRANCH, — Вы ло кализуете место
возникновения ошибки. И тогда
«жучок» в коде связанного списка
не испортит целостности двоичного
дерева, и наоборот. Конечно, всегда остается
веро ятность такой фатальной ошибки в
коде, которая приведет к записи данных
в посто роннюю кучу, но это случается
значительно реже.
Более эффективное
управление памятью
Кучами можно
управлять гораздо эффективнее,
создавая в них объекты одинакового размера.
Допустим, каждая структура NODE занимает
24 байта, а каждая структура BRANCH — 32. Памятьдля
всех этих объектов выделяется из одной
кучи. На рис. 18-2 показано, как выглядит
полностью занятая куча с несколькими
объектами NODE и BRANCH. Если объекты NODE 2 и
NODE 4 удаляются, память в куче становится
фраг ментированной И если после этого
попытаться выделить в ней память для
структу ры BRANCH, ничего не выйдет — даже
несмотря на то что в куче свободно 48 байтов,
а структура BRANCH требует всего 32.
Если бы в каждой куче содержались объекты одинакового размера, удаление од ного из них позволило бы в дальнейшем разместить другой объект того же типа.
Рис. 18-2. Фрагментированная
куча, содержащая несколько объектов NODE
и BRANCH
Локальный доступ
Перекачка страницы
ил оперативной памяти в страничный
файл занимает ощутимое время. Та же задержка
происходит и в момент загрузки страницы
данных обратно в оперативную
память. Обращаясь в основном к
памяти, локализованной в небольшом диапазоне
адресов, Вы снизите вероятность перекачки
страниц между оперативной памятью и страничным
файлом.
Поэтому при
разработке приложения старайтесь размещать
объекты, к которым необходим
частый доступ, как можно плотнее
друг к другу. Возвращаясь к примеру со
связанным списком и двоичным деревом,
отмечу, что просмотр списка не связан
с просмотром двоичного дерева Разместив
все структуры NODE друг за другом в од ной
куче, Вы, возможно добьетесь того, что
по крайней мере несколько структур NODE
умесгятся в пределах одной страницы физической
памяти. И тогда просмотр связанного списка
не потребует от процессора при каждом
обращении к какой-либо структуре NODE переключаться
с одной страницы на другую.
Если же «свалить"
оба типа cтpyктyp в одну кучу, обьекты
NODE необязательно будут размещены строго
друг за другом. При самом неблагоприятном
стечении об стоятельств на странице окажется
всего одна структура NODE, a остальное место
зай мут структуры BRANCH. B этом случае просмотр
связанного списка будет приводить к ошибке
страницы (page fault) при обращении к каждой
структуре NODE, что в ре зультате может
чрезвычайно замедлить скорость выполнения
Вашего процесса.
Исключение издержек,
связанных с синхронизацией потоков
Доступ к кучам
упорядочивается по умолчанию, поэтому
при одновременном обра щении нескольких
потоков к куче данные в ней никогда не
повреждаются. Однако для этого функциям,
работающим с кучами, приходится выполнять
дополнительный код. Если Вы интенсивно
манипулируете с динамически распределяемой
памятью, выпол нение дополнительного
кода может заметно снизить быстродействие
Вашей програм мы. Создавая новую кучу,
Вы можете сообщить системе, что единовременно
к этой кучс обращается только один поток,
и тогда дополнительный код выполняться
не
будет. Но береритесь: теперь Вы берете всю ответственность за целостность этой кучи на себя. Система не станет присматривать за Вами.
Быстрое освобождение
всей памяти в куче
Наконец, использование отдельной кучи для какой-то структуры данных позволяет освобождать всю кучу, не перебирая каждый блок памяти. Например, когда Windows Explorer перечисляет иерархию каталогов на жестком диске, он формирует их дере во в памяти. Получив команду обновить эту информацию, он мог бы просто разру шить кучу, содержащую это дерево, и начать все заново (если бы, конечно, он исполь зовал кучу, выделенную только для информации о дереве каталогов). Бо многих при ложениях это было бы очень удобпо, да и быстродействие тоже возросло бы.
Создание дополнительной
кучи
Дополнительные
кучи в процессе создаются вызовом
HeapCreate
HANDLE HeapCreate( DWORD
fdwOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize);
Параметр fdwOptions
модифицирует способ выполнения операций
над кучей. В нем можно указать
0, HEAP_NO_SERIALIZE, HEAP_GENERATE_EXCEPTIONS или комбинацию
последних двух флагов.
По умолчанию
действует принцип
Просматривает связанный список выделенных и свободных блоков памяти.
Находит адрес свободного блока.
Выделяет новый блок, помечая свободный как занятый.
Добавляет новый
элемент в связанный список блоков памяти.
Флаг HEAP_NO_SERIALIZE
использовать нс следует, и вот почему.
Допустим, два потока одновременно
пытаются выделить блоки памяти из
одной кучи. Первый по ток выполняет
операции по пп 1 и 2 и получает адрес
свободного блокз памяти. Но только он
соберется перейти к третьему этапу, как
сго вытеснит второй поток и тоже выполнит
операции по пп. 1 и 2. Поскольку первый
поток не успел дойти до этапа 3, второй
поток обнаружит тот же свободный блок
памяти,
Итак, оба потока
считают, что они нашли свободный блок
памяти в куче. Поэтому поток 1 обновляет
связанный список, помечая новый блок
как занятый. После этого и поток 2 обновляет
связанный список, помечая тот же блок
как занятый. Ни один из потоков пока ничего
не подозревает, хотя оба получили адреса,
указывающие на один и тот же блок памяти.
Ошибку такого рода обнаружить очень трудно, поскольку она проявляется не сра зу. Но в конце концов сбой произойдет и, будьте уверены, это случится в самый не подходящий момент, Вот какие проблемы это может вызвать.