Киберфак – бесплатно скачать презентации PowerPoint, лекции, рефераты, шпоры, курсовые cyberfac logo
cyberfac.ru
На главную | Регистрация | Вход
  Статьи  
Главная » Статьи » Информатика » Высокоуровневые методы информатики и программирования

Управление памятью (Delphi/C/C++ )

Полезная статья? Пожалуйста, поставьте "+"
К содержанию

Управления памятью в Delphi

Диспетчер памяти
В приложении Delphi диспетчер памяти управляет всеми динамическими выделениями (allocations) и освобождениями памяти. Через него работают стандартные процедуры New, Dispose, GetMem, ReallocMem и FreeMem, равно как и выделение памяти для объектов и длинных строк.
Диспетчер памяти заточен под приложения, выделяющие большое количество небольших объёмов памяти, что является характерным для ООП-приложений и приложений, обрабатывающих строковые данные. Другие менеджеры памяти (такие, как реализации GlobalAlloc, LocalAlloc, а также виндовая поддержка куч (heap)) не являются оптимальными в подобных ситуациях и могут замедлить приложение.
Для обеспечения оптимальной производительности менеджер памяти работает напрямую с ядром виртуальной памяти виндов (Win32 virtual memory API) через функции VirtualAlloc и VirtualFree. Память резервируется 1Mb-ыми секциями и выделяется блоками по 16 Kb по мере надобности.
Блоки всегда выровнены по 4-х байтовой границе и всегда включают 4-х байтовый заголовок, в котором хранятся размер блока и другая статусная информация. Выравнивание по "двойному слову" (double word) гарантирует оптимальную производительность CPU при адресации такого блока.
Диспетчер памяти контролирует две переменные, AllocMemCount и  AllocMemSize, содержащие количество выделенных блоков и общую величину всей выделенной памяти. Эти данные приложение может использовать для отладки.
Модуль System содержит две процедуры, GetMemoryManager и SetMemoryManager, которые могут быть использованы для низкоуровневого перехвата обращений к диспетчеру памяти. Тот же модуль представляет функцию GetHeapStatus, которая возвращает запись, содержащую детальную информацию о статусе диспетчера памяти.

Переменные
Память для глобальных переменных выделяется в сегменте данных приложения и освобождается при его завершении. Локальные переменные живут в стеке (stack). Каждый раз при вызове процедур или функций для них выделяется память, которая освобождается при выходе из процедуры или функции, хотя оптимизация компилятора может их уничтожить гораздо раньше.
Стек приложения определяется двумя значениями: минимальным и максимальным размером. Эти значения задаются директивами компилятора $MINSTACKSIZE (по умолчанию - 16 384 байта) и $MAXSTACKSIZE (по умолчанию - 1 048 576 байт). Винда сообщит об ошибке, если она не сможет при запуске приложения предоставить ему минимальный размер памяти для стека.
Если приложению требуется больше стековой памяти, чем указано в $MINSTACKSIZE, то она выделяется блоками по 4 Kb. Если очередное выделение памяти обламывается, то ли потому, что памяти больше нет, то ли потому, что суммарный объём запрошенной стековой памяти превысил $MAXSTACKSIZE, генерится эксепшн: EstackOverflow, причём контроль переполнения стека является полностью автоматическим, директива $S, когда-то позволявшая его отключить, оставлена только для совместимости с предыдущими версиями.
Динамические переменные, созданные с помощью процедур New или GetMem, размещаются в куче и сохраняются, пока не будут убиты через Dispose и FreeMem соответственно.
Длинные строки, широкие строки (wide strings), динамические массивы, варианты и интерфейсы также размещаются в куче, но выделение памяти под них контролируется диспетчером автоматически.

Описание переменных и функций
AllocMem

function AllocMem(Size: Cardinal): Pointer;
Выделяет в куче блок памяти заданного размера. Каждый байт выделенной памяти выставляется в ноль. Для освобождения памяти используется FreeMem.

AllocMemCount

var AllocMemCount: Integer;
Содержит количество выделенных блок памяти. Эта переменная увеличивается каждый раз, когда пользователь запрашивает новый блок, и уменьшается, когда блок освобождается. Значения переменной используется для определения количества "оставшихся" блоков.

Так как переменная является глобальной и живёт в модуле System, её прямое использование не всегда безопасно. Модули, слинкованные статически, будут иметь разные экземпляры AllocMemCount. Статически слинкованными считаются приложения, не использующие пакеты времени выполнения (runtime packages).

AllocMemSize

var AllocMemSize: Integer;
Содержит размер памяти, в байтах, всех блоков памяти, выделенных приложением. Фактически эта переменная показывает, сколько байтов памяти в данный момент использует приложение. Поскольку переменная является глобальной, то к ней относится всё, сказанное в отношении AllocMemCount.

GetHeapStatus
function GetHeapStatus: THeapStatus;
Возвращает текущее состояние диспетчера памяти.
Код:
type
THeapStatus = record
TotalAddrSpace: Cardinal;s
TotalUncommitted: Cardinal;
TotalCommitted: Cardinal;
TotalAllocated: Cardinal;
TotalFree: Cardinal;
FreeSmall: Cardinal;
FreeBig: Cardinal;
Unused: Cardinal;
Overhead: Cardinal;
HeapErrorCode: Cardinal;
end;

Если приложение не использует модуль ShareMem, то данные в записи TheapStatus относятся к глобальной куче (heap), в противном случае это могут быть данные о памяти, разделяемой несколькими процессами.

TotalAddrSpace -  Адресное пространство, доступное вашей программе в байтах. Значение этого поля будет расти, по мере того, как увеличивается объём памяти, динамически выделяемый вашей программой.
TotalUncommitted -   Показывает, сколько байтов из TotalAddrSpace не находятся в swap-файле.
TotalCommitted -  Показывает, сколько байтов из TotalAddrSpace находятся в swap-файле. Соответственно, TotalCommited + TotalUncommited = TotalAddrSpace
TotalAllocated -  Сколько всего байтов памяти было динамически выделено вашей программой
TotalFree -   Сколько памяти (в байтах) доступно для выделения вашей программой. Если программа превышает это значение, и виртуальной памяти для этого достаточно, ОС автоматом увеличит адресное пространство для вашего приложения и соответственно увеличится значения TotalAddrSpace
FreeSmall -  Доступная, но неиспользуемая память (в байтах), находящаяся в "маленьких" блоках.
FreeBig -  Доступная, но неиспользуемая память (в байтах), находящаяся в "больших" блоках. Большие блоки могут формироваться из непрерывных последовательностей "маленьких".
Unused -   Память (в байтах) никогда не выделявшаяся (но доступная) вашей программой. Unused + FreeSmall + FreeBig = TotalFree.
Overhead -  Сколько памяти (в байтах) необходимо менеджеру кучи, чтобы обслуживать все блоки, динамически выделяемые вашей программой.
HeapErrorCode -  Внутренний статус кучи

TotalAddrSpace, TotalUncommitted и TotalCommitted относятся к памяти ОС, выделяемой для вашей программы, а TotalAllocated и TotalFree относятся к памяти кучи, используемой для динамического выделения памяти самой программой. Таким образом, для отслеживания того, как ваша программа использует динамическую память, используйте TotalAllocated и TotalFree

GetMemoryManager

procedure GetMemoryManager(var MemMgr: TMemoryManager);
Возвращает указатель на текущий диспетчер памяти. Структура TMemoryManager описана ниже.
TMemoryManager - структура данных
Код:
type
PMemoryManager = ^TMemoryManager;

TMemoryManager = record
GetMem: function(Size: Integer): Pointer;
FreeMem: function(P: Pointer): Integer;
ReallocMem: function(P: Pointer; Size: Integer): Pointer;
end;


Эта запись определяет, какие функции используются для выделения и освобождения памяти.
Функция GetMem должна выделить блок памяти размером Size (Size никогда не может быть равным нулю) и вернуть на него указатель. Если она не может этого сделать, она должна вернуть nil.
Функция FreeMem должна освободить память Size по адресу P. P никогда не должен быть равен nil. Если функция с этим справилась, она должна вернуть ноль.
Функция ReallocMem должна перевыделить память Size для блока P. Здесь P не может быть nil и Size не может быть 0 (хотя при вызове ReallocMem не из диспетчера памяти, это вполне допускается). Функция должна выделить память, при необходимости, переместить блок на новое место и вернуть указатель на это место. Если выделение памяти невозможно, она должна вернуть nil.

HeapAllocFlags

var HeapAllocFlags: Word = 2;
Этими флагами руководствуется диспетчер памяти при работе с памятью. Они могут комбинироваться и принимать следующие значения (по умолчанию - GMEM_MOVEABLE):

GMEM_FIXED -   Выделяет фиксированную память. Т.к. ОС не может перемещать блоки памяти, то и нет нужды блокировать память (соответственно, не может комбинироваться с GMEM_MOVEABLE)
GMEM_MOVEABLE  -  Выделяет перемещаемую память. В Win32 блоки не могут быть перемещены, Если они расположены в физической памяти, но могут перемещаться в пределах кучи.
GMEM_ZEROINIT -   При выделении памяти (например, функцией GetMem) все байты этой памяти будут выставлены в 0. (отличная черта)
GMEM_MODIFY   -    Используется для изменения атрибутов уже выделенного блока памяти
GMEM_DDESHARE  -  Введёны для совместимости с 16-разрядными версиями, но может использоваться для оптимизации DDE операций. Собственно, кроме как для таких операций эти флаги и не должны использоваться
GMEM_SHARE  -  "-/-"
GPTR -   Предустановленный, соответствует GMEM_FIXED + GMEM_ZEROINIT
GHND  -  Предустановленный, соответствует GMEM_MOVEABLE + GMEM_ZEROINIT

IsMemoryManagerSet

function IsMemoryManagerSet:Boolean;
Возвращает TRUE, если кто-то успел похерить дефолтовый диспетчер памяти и воткнуть вместо него свой.

ReallocMem

procedure ReallocMem(var P: Pointer; Size: Integer);
Перевыделяет память, ранее выделенную под P. Реальные действия процедуры зависят от значений P и Size.
P = nil, Size = 0: ничего не делается;
P = nil, Size <> 0: соответствует вызову P := GetMem (Size);
P <> nil, Size = 0: соответствует вызову FreeMem (P, Size) (с тем отличием, что FreeMem не будет обнулять указатель, а здесь он уже равен nil).
P <> nil, Size <> 0: перевыделяет для указателя P память размером Size. Текущие данные никак не затрагиваются, но если размер блока увеличивается, новая порция памяти будет содержать всякий мусор. Если новый блок "не влазит" на своё старое место, он перемещается на новое место в куче и значение P обновляется соответственно. Это важно: после вызова данной процедуры блок P может оказаться в памяти по совсем другому адресу!

SetMemoryManager

procedure SetMemoryManager(const MemMgr: TMemoryManager);
Устанавливает новый диспетчер памяти. Он будет использоваться при выделении и освобождении памяти процедурами GetMem, FreeMem, ReallocMem, New и Dispose, а также при работе конструкторов и деструкторов объектов и работе с динамическими строками и массивами.
SysFreeMem, SysGetMem, SysReallocMem
Используются при написании собственного диспетчера памяти.

 

Управления памятью в C/C++

Стоимость операций выделения и освобождения памяти – одна из самых дорогостоящих операций. Особенность операций работы с памятью – сложность их автоматической оптимизации. Иными словами, управление памятью отдается на откуп программисту. В статье рассматривается пример, который показывает, как оптимизация операций работы с памятью влияет на производительность алгоритмов.

Для выполнения операций выделения и освобождения памяти в языках C/C++ обычно используется один из 2х подходов:
встроенные механизмы управления памятью – malloc(new), free(delete);
специализированные диспетчеры памяти;

Основное достоинство и недостаток функций malloc/free, new/delete – их универсальность. Достоинство в том, что данные функции можно применять повсеместно, недостаток – их производительность. Управление памятью с помощью универсальных функций выглядит следующим образом: Функция malloc(new) имеет список доступных и занятых блоков памяти и при каждом выделении памяти просматривает данный список в поиске свободного блока необходимого размера. В том случае если она не может найти блок подходящего размера она может выполнить ряд оптимизирующих операций со свободными блоками и выделить в конечном итоге память, либо выдать предупреждение об ошибке выделения, если блока подходящего размера найдено не было. Обратным действием обладает функция free(delete), которая освобождает блоки памяти, помечая их соответствующим образом в списке блоков.

При интенсивном использовании функций malloc(new)/free(delete) может возникать большая фрагментация памяти, что приведет к замедлению последующих операций. Кроме того, работа со списками блоков памяти приводит к менее быстрым операциям работы с памятью.

Зачастую высокопроизводительные приложения используют сложные структуры данных и нетривиальные алгоритмы, в которых используется много операций с памятью, причем разработчик знает оптимальный способ управления памятью. В данном случае для достижения максимальной производительности разработчики создают специализированные функции управления памятью (перегружают операторы new/delete в случае C++). Как правило, общий подход заключается в выделении большого объема памяти изначально и использовании его специализированными диспетчерами для хранения структур данных с использованием оптимизированных структур управления памятью в этом блоке. Реализация диспетчеров управления памятью может быть весьма нетривиальна.

В качестве примера рассмотрим простой код на языке C, который использует универсальный подход и его аналог, который использует специализированный подход. Задача – выделить память под матрицу NxM динамически, а затем освободить занятую память.

Стандартный механизм
#define N 10
#define M 20
int ** matrix;
int i;
matrix = (int **) malloc (N * sizeof(int*));
for(i = 0 ; i < N ; i ++)
{
martrix[i] = (int*)malloc(M*sizeof(int));
}


for(i = 0 ; i < N ; i ++)
{
free(martrix[i]);
}
free(matrix);

Специализированный механизм
#define N 10
#define M 20
int ** matrix;
int i;
matrix = (int **) malloc (N * sizeof(int*));
matrix[0] = (int *) malloc(N * M * sizeof(int));
for(i = 1 ; i < N ; i ++)
{
martrix[i] = matrix[0] + i * M;
}


free(matrix[0]);
free(matrix);

Например, алгоритм LCSS (longest common substring), использующий вторую модель работы с памятью работает в 3 раза быстрее, чем алгоритм, использующий стандартную модель выделения памяти. Очевидно, что используя оптимальную модель памяти можно значительно увеличить быстродействие приложений.

Большинство высокопроизводительных приложений (например, СУБД Oracle) используют собственные диспетчеры памяти, что позволяет добиться колоссального прироста производительности по сравнению со стандартными диспетчерами. Необходимо заметить так же, что библиотеки для разработки приложений на языках программирования C/C++ имеют стандартные модули для создания диспетчеров, например библиотека классов для C++ Boost (Boost Pool Library).


Категория: Высокоуровневые методы информатики и программирования | Добавил: Ni-Cd (01 Декабря 2011)
Просмотров: 2837 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
  Полезные материалы  

В нашем каталоге файлов можно найти много полезной информации. Также советуем заглянуть в каталог статей: в нем есть полезные статьи по темам: Экономика предприятия, Общая экономика, Финансы и Кредит, также Словарь терминов по экономике, Маркетинг, Бухучет и Мировая экономика
Также есть полезная страница Факультеты МИФИ, которая расскажет о том, какие есть в МИФИ факультеты.
Меню
 

Навигация
Высокоуровневые методы информатики и программирования [28]
Информатика и программирование [34]
Информационные системы в экономике [36]
Языки программирования и методы трансляции [15]
Алгоритмизация и программирование [61]
 

Поиск
 

Онлайн
Онлайн всего: 1
Гостей: 1
Пользователей: 0
 

Статистика


Рейтинг@Mail.ru

 


2007 - 2022 © Ni-Cd. All Rights Reserved