Когда только изучать 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:
- родной от FPC
- не родной, но, тем не менее, упоминаемый в wiki Free Pascal — cmem
- предложенный Алексеем 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 и т.д.









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