Большой объем элементов и список и «странное» распределение памяти - PullRequest
0 голосов
/ 04 марта 2012

В последние дни я видел что-то странное в распределении памяти Delphi при работе с большим количеством элементов в списке.

Мой компьютер имеет 12 ГБ оперативной памяти;когда я запускаю свое приложение в 64-битном режиме (хранение данных в списке, элементы большого объема), я вижу, что используемая моим компьютером память медленно увеличивается на 10 ГБ и через короткое время уменьшается, пока использование не падает почти до 8 МБ (нормальноразмер приложения при запуске в памяти).Я подумал: если я храню данные в памяти, размер используемой памяти не должен быть таким же?Потому что почему тогда использование памяти уменьшается с 8 МБ до 10 ГБ и уменьшается до 8 МБ, когда каждый большой объем данных сохраняется в памяти и не очищается?

Это нормально?Спасибо за любые предложения.

ОБНОВЛЕНИЕ

Я сообщаю полный код, где возможно воспроизвести о том, что я сказал ранее.

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  PsApi, windows, System.SysUtils, System.Generics.Collections;

type
  TMyRecord = record
    x: Integer;
    y: Integer;
  end;
  TMyArr = array of TMyRecord;
  TMyList = TList<TMyArr>;

function CurrentMemoryUsage: Cardinal;
var
  pmc: TProcessMemoryCounters;
begin
  pmc.cb := SizeOf(pmc);
  if GetProcessMemoryInfo(GetCurrentProcess, @pmc, SizeOf(pmc)) then
    Result := pmc.WorkingSetSize
  else
    RaiseLastOSError;
end;

var
  MyArr: TMyArr;
  MyList: TMyList;
  iIndex1, iIndex2: Integer;
  iMax: Int64;
begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
    iMax := Low(Int64);
    Writeln(FormatFloat('Memory used before to create list: ,.# K',
      CurrentMemoryUsage / 1024));
    MyList := TMyList.Create;
    try
      Writeln(FormatFloat('Memory used before load item in list: ,.# K',
        CurrentMemoryUsage / 1024));
      for iIndex1 := 0 to 20000000 do
      begin
        SetLength(MyArr, 100);
        for iIndex2 := 0 to High(MyArr) do
        with MyArr[iIndex2] do
        begin
          x := iIndex2 + 1;
          y := iIndex2 - 1;
        end;
        MyList.Add(MyArr);
        if CurrentMemoryUsage > iMax then
          iMax := CurrentMemoryUsage;
      end;
      Writeln(FormatFloat
        ('Memory used (max) during to load item in list: ,.# K', iMax / 1024));
      Writeln(FormatFloat('Memory used to end load item in list: ,.# K',
        CurrentMemoryUsage / 1024));
    finally
      MyList.Free;
    end;
    Writeln(FormatFloat('Memory used after destroyed list: ,.# K',
      CurrentMemoryUsage / 1024));
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;

end.

Как вывод яесть что-то так:

Memory used before to create list: 3.452 K
Memory used before load item in list: 3.504 K
Memory used (max) during load item in list: 4.194.300 K
Memory used to end load item in list: 2.789.020 K
Memory used after destroyed list: 2.976 K

В этом случае, где сказать «во время загрузки элемента в списке», я получаю почти 4 ГБ, но после того, как я закончил, чтобы загрузить элемент в списке памяти используется почти 2-3 Гб.Я пытался много симулировать, это изменение значения не является постоянной величиной, но в целом это значение учитывается.Повторюсь, для меня это не так важно.Был только для понимания больше о распределении памяти.

Ответы [ 2 ]

3 голосов
/ 05 марта 2012

Из ваших ответов на комментарии вы помещаете миллионы записей в список.Вы не указали тип списка, но если это TList или потомок, он автоматически увеличит список до размера.

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

Память для старых списков не освобождается / не может быть повторно использована как немедленнаяЯ полагаю, что это является причиной вашего довольно большого использования памяти.

Если вы знаете размер списка, который вам понадобится, вы должны использовать свойство Capacity для выбора массива, если не добавили какой-то дополнительный код в ваш циклчтобы отслеживать текущий счет в списке и, если он близок к емкости, увеличить емкость будет гораздо больше, чем на 25%, что будет автоматически.Это значительно сократит количество выделенной памяти и, следовательно, использование памяти (также улучшит производительность).

Надеюсь, это поможет.

Обновление Измененный ответ для удаления неправильного изменения размера спискаинформация, указанная Кохи.

1 голос
/ 06 марта 2012

@ Dampsquid прав, но потомки TList не растут с фиксированными приращениями по 16 элементов и не делали этого в течение длительного времени (D3?). Как только вы получите 64 элемента, он увеличивается на 25% при каждом изменении размера. Вы можете убедиться в этом сами, если не используете бесплатную версию Delphi. Ctrl-клик по объявлению TList и осмотрите его. Add () вызывает Grow (), который выглядит следующим образом:

  procedure TList.Grow;
  var
    Delta: Integer;
  begin
    if FCapacity > 64 then
      Delta := FCapacity div 4
    else
      if FCapacity > 8 then
        Delta := 16
      else
        Delta := 4;
    SetCapacity(FCapacity + Delta);
  end;

Вы все еще в гораздо лучшем положении, даже догадываясь о возможной мощности. Если вы знаете, что собираетесь использовать миллионы записей, начните с установки емкости на миллион. Таким образом, вы пропустите 30 с лишним операций изменения размера, необходимых для увеличения вашего списка до этого миллиона. Я бы соблазнился ошибиться и угадать наибольшее число, которое, по вашему мнению, вам понадобится. Вы «тратите» 8 байтов на каждую дополнительную запись, но избегаете большого количества операций перераспределения и копирования, поэтому неправильная обработка нескольких миллионов стоит всего 16 МБ. Когда вы используете ГБ памяти, это ничего.

(извините за ответ, у меня недостаточно очков, чтобы комментировать).

...