О производительности динамических массивов...

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Аватара пользователя
beria
постоялец
Сообщения: 130
Зарегистрирован: 29.09.2016 07:57:13

О производительности динамических массивов...

Сообщение beria »

Простой тест, с одинаковым функционалом для динамических массивов и простого распределения памяти для ссылочных переменных

Код: Выделить всё

program project1;

{$mode objfpc}{$H+}

uses
 {$IFDEF UNIX}
  cthreads,
     {$ENDIF}
  Classes,
  SysUtils { you can add units after this };

  var
  Ar: array of byte;
  P:  PByteArray;
  i,t : longint;
begin
  t := GetTickCount64;
  for i := 0 to 100000 do
  begin
    SetLength(Ar, 1000000);
    Ar[100] := 100;
    SetLength(Ar, 500);
    if Ar[100] <> 100 then writeln('error:SetLength');
    Ar[9] := 9;
    SetLength(Ar, 0);
  end;
  writeln(GetTickCount64 - t);
  t := GetTickCount64;
  for i := 0 to 100000 do
  begin
    GetMem(P, 1000000);
    (P^)[100] := 100;
    ReallocMem(P, 500);
   if (P^)[100] <> 100 then writeln('error:ReallocMem');
    (P^)[9] := 9;
    FreeMem(P);
  end;
  writeln(GetTickCount64 - t);
  readln;
end.
Результат работы:
5187
16
то есть второй вариант более чем в 100 раз быстрее.. А тогда, вопрос, а какие такие особые фичи, ну кроме того что выход из программы удалит и все массивы, дают динамические массивы, если по скорости работы (используется стандартный менеджер памяти), они проигрывают...
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

https://www.freepascal.org/docs-html/rt ... ength.html
>>In case the length is set to a larger length than the current one, the new elements are zeroed out for a dynamic array. For a string, the string is zero-terminated at the correct length.
iskander
энтузиаст
Сообщения: 630
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

beria писал(а):А тогда, вопрос, а какие такие особые фичи, ну кроме того что выход из программы удалит и все массивы, дают динамические массивы
Прозрачная инициализация/финализация элементов массива.
Аватара пользователя
runewalsh
энтузиаст
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Сообщение runewalsh »

GetMem не заполняет память нулями, SetLength заполняет (или даже вызывает кастомные Initialize/Finalize, что ещё медленнее). Если использовать GetMem+FillChar, будет одинаково.

Но если ты вызвал SetLength, то ты эти элементы собираешься использовать, то есть будешь с ними делать что-то, на фоне чего их зануление потеряется. Попробуй добавить в цикл хоть какую-то обработку, например, подсчёт суммы всех элементов.
Alex2013
долгожитель
Сообщения: 3240
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

runewalsh писал(а):GetMem не заполняет память нулями, SetLength заполняет (или даже вызывает кастомные Initialize/Finalize, что ещё медленнее). Если использовать GetMem+FillChar, будет одинаково.
Все еще хуже! SetLength в цикле натуральная "смерть хомячкам" . ( я когда то смотрел код и там по сути масив ЦЕЛИКОМ пересоздается с копированием содержимого в новую область ). Зачем так сделано можно только догадываться .Так что лучше считать что SetLength срабатывает только один раз при создании массива ( ну еще освободить память можно ).

Для практики лучше высчитать максимальный размер заранее и "не мучать скотинку" (SetLength ) лишний раз. Если содержимое масиа неважно то можно обнулить создать по новой. Но лучше уж отказаться от такого "ненавязчивого сервиса" например в пользу TList и его производных.
Последний раз редактировалось Alex2013 07.09.2022 11:48:58, всего редактировалось 2 раза.
Аватара пользователя
beria
постоялец
Сообщения: 130
Зарегистрирован: 29.09.2016 07:57:13

Сообщение beria »

runewalsh писал(а):GetMem не заполняет память нулями, SetLength заполняет (или даже вызывает кастомные Initialize/Finalize, что ещё медленнее). Если использовать GetMem+FillChar, будет одинаково.

Но если ты вызвал SetLength, то ты эти элементы собираешься использовать, то есть будешь с ними делать что-то, на фоне чего их зануление потеряется. Попробуй добавить в цикл хоть какую-то обработку, например, подсчёт суммы всех элементов.
Целых два разных рассчетных цикла в разных состояниях..

Код: Выделить всё

program project1;

{$mode objfpc}{$H+}

uses
 {$IFDEF UNIX}
  cthreads,
        {$ENDIF}
  Classes,
  SysUtils { you can add units after this };

const
  loop = 100000;
  loop2 = 200;

var
  Ar: array of byte;
  P: PByteArray;
  i, j, k, w, t: longint;
begin
  t := GetTickCount64;
  for i := 0 to loop do
  begin
    SetLength(Ar, loop);
    for j := 0 to loop - 1 do Ar[j] := j;
    k := 0;
    for  j := 0 to loop - 1 do Inc(k, Ar[j]);
    SetLength(Ar, loop2);
    w := 0;
    for  j := 0 to loop2 - 1 do Inc(w, Ar[j]);
    SetLength(Ar, 0);
  end;
  writeln(GetTickCount64 - t);
  writeln(k);
  writeln(w);
  t := GetTickCount64;
  for i := 0 to loop do
  begin
    GetMem(P, loop);
    for j := 0 to loop - 1 do (P^)[j] := j;
    k := 0;
    for  j := 0 to loop - 1 do Inc(k, (P^)[j]);
    ReallocMem(P, loop2);
    w := 0;
    for  j := 0 to loop2 - 1 do Inc(w, (P^)[j]);
    FreeMem(P);
  end;        
  writeln(GetTickCount64 - t);
  writeln(k);
  writeln(w);
  readln;
end. 

Результат
9875
12742320
19900
7437
12742320
19900

То есть и тут динамический массив медленнее, пусть и на всего 30%, но тоже прилично.... Подозреваю, что время доступа по ссылке, одинаково, а вот перераспределения памяти у массива медленнее.. Кстати размер выделенной памяти можно получить через MemSize(p)

зы: "GetMem не заполняет память нулями, " Имхо совсем не нужная везде задача и даже лишняя, как и дефолтное обнуление всех переменных из var. Тем более fillChar, если надо универсальнее...
Alex2013
долгожитель
Сообщения: 3240
Зарегистрирован: 03.04.2013 11:59:44

Сообщение Alex2013 »

Придания глубокой старины.
Так работает SetLength
(2 "с копейками" секунды это даже не заполнение, а удаление что еще веселее )

Изображение
Это у Зуба у меня на "рабочей лошадке"(Старый ноутбук с трудом тянущий Win10 ) сейчас вообще
Press AddItems or RemoveItems 0.334sec 9.443sec (64 бита) и 0.535sec 9.302sec (32 бита) )
А так Tlist и TStringlist
Изображение
(это 32 бита на 64 битной оси в режиме 64 бита вообще 0.0127 c )
А очищает у меня так " Список очищен за 0.0012 c" (Иногда вообще "0 с" что показывает что расчет времени сотых и тысячных долях секунды просто погрешность вычислений )



См. тему
Посоветуйте компонент типа ListView но с форматированием
Последний раз редактировалось Alex2013 07.09.2022 14:38:27, всего редактировалось 1 раз.
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>зы: "GetMem не заполняет память нулями, " Имхо совсем не нужная везде задача и даже лишняя, как и дефолтное обнуление всех переменных из var. Тем более fillChar, если надо универсальнее...
если ненужная, используешь классические массивы. Скорость одинаковая, за исключением этого момента
Аватара пользователя
runewalsh
энтузиаст
Сообщения: 579
Зарегистрирован: 27.04.2010 00:15:25

Сообщение runewalsh »

beria писал(а):Результат
9875
12742320
19900
7437
12742320
19900
У меня, без изменений кода, x86-64, -O3:
9031
12742320
19900
9578
12742320
19900
:>
Аватара пользователя
beria
постоялец
Сообщения: 130
Зарегистрирован: 29.09.2016 07:57:13

Сообщение beria »

runewalsh писал(а):У меня, без изменений кода, x86-64, -O3
Ещё раз пересобрал. Все цифры (+- немного на погрешность) повторились. Подозреваю у вас где-то включена отладка, что все объясняет.... FPC- транк c гитлаба.

Добавлено спустя 1 минуту 31 секунду:
zub писал(а): используешь классические массивы.
Классические массивы могут динамически менять размер? :shock: :shock: :shock: :shock:
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

beria, вы в своём коде после GetMem не вызываете FillChar(P^, loop, 0), скорее всего из-за этого возникает такая разница. У меня FillChar уравнивает показатели производительности.
Аватара пользователя
beria
постоялец
Сообщения: 130
Зарегистрирован: 29.09.2016 07:57:13

Сообщение beria »

Дож писал(а):GetMem не вызываете FillCha
А зачем? Сначала обнулять, и только потом заполнять данными - бессмысленная и по моему ненужная вещь. Поэтому я выше и написал что " перераспределения памяти у массива медленнее"
Если чисто распределение памяти для данных, как в первом тесте показано - так вообще GetMem работает более чем в 150 раз быстрее
Аватара пользователя
Дож
энтузиаст
Сообщения: 900
Зарегистрирован: 12.10.2008 16:14:47

Сообщение Дож »

А зачем?
Чтобы не подозревать массив в "медленном перераспределении".
iskander
энтузиаст
Сообщения: 630
Зарегистрирован: 08.01.2012 18:43:34

Сообщение iskander »

beria писал(а):А зачем? Сначала обнулять, и только потом заполнять данными - бессмысленная и по моему ненужная вещь.
А попробуйте проверить это предположение для строк, например.
zub
долгожитель
Сообщения: 2890
Зарегистрирован: 14.11.2005 22:51:26
Контактная информация:

Сообщение zub »

>>лассические массивы могут динамически менять размер? :shock: :shock: :shock: :shock:
а че нет? выделяешь память, копируешь старое, и не инитишь новое. какраз ка ты хочешь

>>А попробуйте проверить это предположение для строк, например.
строки какраз SetLength`ом не инятятся, в там только в конце 0 пишется, в середине мусор будет.
Ответить