Распределение памяти в Borland Delphi - проблемы и решения

Василий Мошнинов [vmoshninov@newmail.ru]vmoshninov@newmail.ru

Недавно я получил письмо о том, что мой плагин ProxyFtp не может показать каталог, содержащий порядка 4000 файлов. Плагин выдавал сообщение "Out of memory". В памяти при этом ФАР занимал около 64Mb. Я решил проверить этот момент и, к сожалению, все мои опасения подтвердились. После анализа ситуации оказалось, что частый вызов борландовской функции ReallocMem приводил к большому объему расходуемой памяти и в последствии вообще переставал работать. Тогда я решил заменить все функции работы с памятью на соответствующие функции Win32 API , благо их использовалось у меня всего 3 штуки:

Просто отказаться от использования ReallocMem я не мог - она используется для создания списка TPluginPanelItem в функции GetFindData. С первыми двумя функциями проблем не было вообще никаких. Вот как они стали выглядеть у меня (функция RaiseLastWin32Error описана в модуле sysutils.pas):

procedure MemAlloc(var lpMem; Size: integer);
begin
  pointer(lpMem):= pointer( GlobalAlloc (LPTR, Size));
  if pointer(lpMem) = nil then
    RaiseLastWin32Error;
end; { procedure MemAlloc }

procedure MemFree(var lpMem);
begin
  pointer(lpMem):= pointer( GlobalFree (HGLOBAL(lpMem)));
  if pointer(lpMem) <> nil then
    RaiseLastWin32Error;
end; { procedure MemFree }

Небольшие затруднения возникли с функцией ReallocMem. Дело в том, что частый вызов GlobalRealloc из Win32 API также приводил к ошибке (что-то про "Insufficient storage...") Тогда я, основываясь на потребностях плагина, сделал эту функцию как вызов двух предыдущих с некоторыми ограничениями.

Функция первый раз возвращает требуемое количество памяти. Во все следующие вызовы память резервируется "с запасом" - кратно константе MEM_DELTA. Это позволило избежать частых распределений памяти при запросах небольшими порциями:

procedure MemRealloc(var lpMem; uSize: integer);
const
  MEM_DELTA = $2000; // 8192 bytes
var
  P: pointer;
  Size: integer;
begin
  if uSize = 0 then
    MemFree(lpMem)
  else if pointer(lpMem) = nil then
// Первый раз вернем столько, сколько запросили
    MemAlloc(lpMem, uSize)
  else
  begin
// Требуемый размер, выровненный на границу MEM_DELTA
    uSize := (uSize + (MEM_DELTA - 1)) and not (MEM_DELTA - 1);
// Текущий размер захваченной памяти
    Size:= GlobalSize(HGLOBAL(lpMem));
    if (uSize  > Size) or ((Size - uSize) > MEM_DELTA) then
// Запросили больше распределенного размера или уменьшили на величину, большую
// MEM_DELTA
    begin
      MemAlloc(P, uSize);
      if Size > uSize then
        Size:= uSize;
      Move(pointer(lpMem)^, P^, Size);
      MemFree(lpMem);
      pointer(lpMem):= P;
    end;
  end; { else }
end; { procedure MemRealloc }

Когда я заменил все вызовы стандартных функций работы с памятью на эти, FAR смог спокойно показать 4600 файлов (раньше падал на 3800), при этом занял в оперативной памяти около 10 Mb (против начальных 64 Mb) и скорость построения списка TPluginPanelItem возросла до ~3 сек (против ~30 сек в начале).

Естественно, в каждом конкретном случае надо смотреть величину MEM_DELTA. Плюс накладывается ограничение на размер в 1 Gb, вместо 2 Gb. Однако, судя по работе плагина - оно того стоило :))

Новые функции работы с памятью задействованы в ProxyFtp 1.6.r3 и в ProxyNews 1.0 Если ваш плагин не может показать каталог, выдавая ошибку "Out of memory" - проверьте его версию и в случае необходимости - обновите:


 
14.04.2000
наверх