12 дек. 2019 г.

О пользе свойства Capacity

    Многие классы, у которых есть список элементов, имеют свойство "Capacity" или его аналог. Можно всю жизнь программировать и не догадываться о его существовании. Но оно есть. Так зачем оно нужно и как его использовать?
    Каждое добавление нового элемента в список, приведет к перераспределению памяти, которая выделена под массив элементов этого списка. Когда элементов не много - это не важно, но когда их много, постоянное перераспределение памяти может замедлить вашу программу.
    Что бы избежать перераспределения памяти при добавлении нового элемента в список и необходимо свойство Capacity. Оно позволяет заранее распределить память под массив элементов и тем самым повысить производительность программы. Проведем небольшой тест:
uses
  System.Classes,
  System.SysUtils,
  System.Generics.Collections,
  System.Diagnostics;

const
  ciCount = 1000000;

procedure CapacityTest;
var
  l: TList<integer>;
  sw: TStopwatch;
  i, j: integer;
begin
  sw := TStopwatch.StartNew;
  for j := 1 to 10 do
    begin
      l := TList<integer>.Create;
      try
        for i := 1 to ciCount do
         l.Add(i);
      finally
        l.Free;
      end;
    end;
  sw.Stop;
  WriteLn(FormatFloat('0.0', sw.ElapsedMilliseconds / 10));

  sw := TStopwatch.StartNew;
  for j := 1 to 10 do
    begin
      l := TList<integer>.Create;
      l.Capacity := ciCount;
      try
        for i := 1 to ciCount do
         l.Add(i);
      finally
        l.Free;
      end;
    end;
  sw.Stop;
  WriteLn(FormatFloat('0.0', sw.ElapsedMilliseconds / 10));
end;
Результаты:
Количество элементов Разрядность программы, бит Время без Capacity, мс Время с Capacity, мс Прирост скорости, %
1 000 000 32 5,2 3,0 42,31
1 000 000 64 5,1 3,2 37,26
10 000 000 32 59,2 31,5 46,79
10 000 000 64 61,4 34,9 43,16
10 0000 000 32 528,8 323,1 38,90
10 0000 000 64 525,6 350,8 33,26
Как вы видите, предварительное распределение памяти под массив элементов списка дает существенное увеличение производительности.
    Если количество элементов точно не известно, то можно распределить память под какое-то приблизительное количество элементов. Если список будет заполнен до предела, то свойство Capacity при добавлении нового элемента увеличивается автоматически. Подобное увеличение свойства Capacity можно сделать и "руками" вызвав метод Expand, который, если список заполнен, сам рассчитает новое значение Capacity. Для использованного в моем примере TList<integer> автоматическое расширение Capacity увеличивает его значение на 50% уже начиная с Capacity=66. Поэтому, при миллионах элементов можно распределить много лишней памяти или даже поймать "Out of memory". Значит свойство Capacity лучше контролировать самому и увеличивать его с определенным шагом. Например:
l.Capacity := l.Capacity + 1000000;
Если вы добавили все элементы в список, а их количество оказалось меньше, чем Capacity, то не используемую память необходимо освободить с помощью присвоения свойству Capacity значения свойства Count:
l.Capacity := l.Count;
или можно вызвать реализующий это присвоение inline-метод TrimExcess.

    Таким образом, знать о свойстве Capacity и уметь с ним работать может быть весьма полезно.

Комментариев нет: