уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Когда только изучать Lazarus и FPC, то встречал на просторах Интернет достаточно много информации по поводу менеджера памяти Free Pascal. Причем, информация эта носила самые разные оттенки — от крайне негативных отзывов (менеджер памяти тормозной), до положительных (спроектирован грамотно, лучше для разработки под разные ОС не придумать) и нейтральных (хорошо спроектирован, но иногда подводит скорость). Встречал и альтернативные менеджеры памяти для Lazarus/FPC, но до их использования как-то дошел.

На сегодняшний день мой «круг интересов» в плане разработки ограничен небольшими утилитами в области моей профессиональной деятельности, где сверхбыстрые математические вычисления и парсинг JSON размером в 6 Гб занимают, наверное, 0,0001% от всей работы. Однако, в комментариях к статье «Работа с JSON в Lazarus/Free Pascal»  Kazantsev Alexey справедливо предположил, что на скорость парсинга JSON может повлиять замена родного для FPC менеджера памяти на менеджер памяти от Synopse/mORMot2. И мне стало интересно: как сильно повлияет замена менеджера памяти в Lazarus/FPC на скорость работы парсера JSON?

Что имеем на старте

На старте имеем файл с JSON-объектом, в котором содержится 10 массивов по 500 000 элементов каждый. Размер файла — 60 Мб.

Менеджеры памяти для Lazarus/FPC:

  1. родной от FPC
  2. не родной, но, тем не менее, упоминаемый в wiki Free Pascal — cmem
  3. предложенный Алексеем fpcx64mm.pas

Ради «спортивного» интереса распарсим тот же JSON в Delphi с менеджером памяти FastMM5 о котором узнал из блога «ИТ-записки Чорнага кашака«.

Результаты

Код для теста оставил тот же самый, что и в статье про JSON в Lazarus/FPC:

var
  S: TFileStream;
  JSON: TJSONParser;
  JSONData: TJSONData;
  i: integer;
begin
  //считываем файл в TFileStream
  S := TFileStream.Create('json.txt', fmOpenRead);
  try
    for I := 1 to 10 do
    begin
      //засекаем время от момента создания TJSONParser
      Json:=TJSONParser.Create(S, DefaultOptions);
      try
         JSONData:=JSON.Parse;
      finally
        FreeAndNil(JSONData);
        FreeAndNil(JSON);
      end;
      //до момента уничтожения объектов 
      S.Position:=0;
    end;
  finally
    FreeAndNil(S);
  end;
end;

Родной менеджер памяти Free Pascal

Что показал «Диспетчер задач»

Учитывая, что потребление памяти постоянно увеличивалось в процессе парсинга, скриншот постарался сделать в момент, когда разбор JSON подходил к финалу, т.е. примерно на 20-21 секунде теста.

Как и ожидалось, результат был практически тот же самый, что и в предыдущий раз:

Примерно 21 секунда на разбор JSON, включая создание и уничтожение необходимых объектов.

CMEM

Подключаем модуль cmem первым в uses:

program json;
 
{$mode objfpc}{$H+}
uses
  cmem, Interfaces, // this includes the LCL widgetset
  ....
  { you can add units after this };

Начинаем тестировать парсинг JSON и…я честно прождал отклика программы  минут пять. Вот что показывал «Диспетчер задач» при запуске программки:

Вот, что показал «Диспетчер задач», когда я устал ждать хоть какого-то результата:

Спустя пять минут потребление памяти начало уменьшаться примерно на 1-1,5 Мб/с. Решил дождаться результата хотя бы одного теста и дождался:

827,5 секунд!  

Возможно, что cmem в некоторых случаях ускоряет работу многопоточных приложений, о чем сказано здесь, но у меня приложение не многопоточное, а результат получился такой, как будто я не JSON парсил, а рассчитывал траекторию баллистической ракеты на приставке Dendy. Нам такой хоккей не нужен…

fpcx64mm

Постарался отловить момент максимального потребления памяти:

Но, по-моему, в какой-то момент проскакивало значение порядка 450 Мб. Результаты теста получились следующие:

Возможно, что, используя настройки этого менеджера памяти, можно получить ещё более впечатляющий результат, но то,что результат оказался в 10 раз лучше, чем с родным менеджером памяти Free Pascal уже о чем-то да говорит.

Дефолтный менеджер памяти в Delphi 10.3 Rio

Код теста взял из вот этой статьи про JSON в Delphi.

var
  S: TStringStream;
  JSON: TJSONObject;
  t_parse: TStopwatch;
  Bytes: TArray;
begin
  //считываем файл в TStringStream
  S := TStringStream.Create('', TEncoding.UTF8);
  try
    S.LoadFromFile('array.txt');
    Bytes := S.Bytes;
  finally
    FreeAndNil(S);
  end;
 
  for var I := 1 to 10 do
  begin
    //засекаем время от момента создания TJSONObject
    t_parse := TStopwatch.StartNew;
    JSON := TJSONObject.Create;
    try
      //парсим JSON
      JSON.Parse(Bytes, 0);
    finally
      FreeAndNil(JSON);
    end;
    //останавливаем таймер после уничтожения TJSONObject
    t_parse.Stop;
    //выводим результаты теста в TStringGrid
    Memo1.Lines.Add(t_parse.ElapsedMilliseconds.ToString);
  end;
end;

Что показывал диспетчер задач:
Результат работы в миллисекундах:

FastMM5 в Delphi 10.3 Rio

Скачивал FastMM5 вот отсюда. Подключил FastMM5 первым модулем в uses.

program Project18;
 
uses
 FastMM5, Vcl.Forms,
  Unit17 in 'Unit17.pas' {Form17};

Результат в Диспетчере задач

Результат работы в миллисекундах:

Возможно FastMM5 где-то и работает быстрее, но родной дефолтный менеджер Delphi 10.3 на миллисекунды, но оказался по-быстрее при парсинге JSON.

Итог

Для наглядности ниже приведена гистограмма с информацией по всем менеджерам памяти в Delphi и FPC и таблица с результатами

CMem убрал из гистограммы, так как с ним все остальные столбцы превращались в точку. Также, для CMem во всех случаях показан один и тот же результат, так как ждать 2 часа пока программка закончит работу как-то не хотелось.

№ теста Lazarus/FPC
Delphi
fpc cmem fpcx64mm FastMM4 FastMM5
1 21465 827500 2207 910 989
2 21590 827500 2209 910 986
3 21324 827500 2216 901 979
4 21475 827500 2196 915 988
5 21414 827500 2188 911 982
6 21501 827500 2194 912 991
7 21469 827500 2198 927 989
8 21730 827500 2189 921 984
9 21504 827500 2195 914 984
10 21759 827500 2205 917 984

По результатам могу сказать, что предложенный Алексеем менеджер памяти оказался довольно шустрым, особенно на фоне родного для fpc и не родного cmem. Да, парсинг JSON в Delphi всё равно оказался быстрее, но, при этом также надо учитывать и то, что время засекалось от момента создания объекта до полного уничтожения. В Delphi — это один объект TJsonObject, в Lazarus же, чтобы избежать утечки памяти при работе с TJsonParser необходимо «убивать» два объекта — сам парсер и TJsonData, который создается при парсинге JSON. Что касается менеджера от Synopse, то планирую его также протестировать на одной программке под Linux, в которой происходит очень много математических вычислений — от сложения и вычитания, до решения линейных уравнений и возведения в степени типа 3/7, 4/3 и т.д.

уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
5 2 голоса
Рейтинг статьи
Подписаться
Уведомить о
2 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Kazantsev Alexey
Kazantsev Alexey
17/05/2020 20:52

Да, парсинг JSON в Delphi всё равно оказался быстрее

Тут есть небольшое но. В Delphi, JSONObject представляет из себя просто список парных значений, в то время, как во Free Pascal он реализован через хеш-таблицу. Вставка в хеш-таблицу происходит медленнее, однако, доступ к значениям по именам будет работать быстрее.