В этой статье я проведу сравнительный анализ DataSet'ов, которые держат данные в оперативной памяти.

Список DataSet'ов


  1. TJvMemoryData
    Разработчик: сообщество JEDI Visual Component Library (JCL + JVCL)
    JCL (версия 2.8)
    JVCL (версия 3.50)
    > Официальный сайт
  2. TMemTableEh
    Разработчик: EhLib
    Версия: 9.0.040
    > Официальный сайт
  3. TdxMemData
    Разработчик: DevExpress
    Версия: 15.2.2
    > Официальный сайт
  4. TkbmMemTable
    Разработчки: Components4Developers
    Версия: 7.74.00 Professional Edition
    > Официальный сайт

Параметры сравнения DataSet'ов


  1. Вставка записей
  2. Сортировка записей

Окружение

Delphi 10.2 Tokyo Starter
Операционная система Windows 7 SP1 Ultimate x64
Процессор Intel Core i5
ОЗУ 8 Гб

Тестовые данные


DataSet'ы будут тестироваться на данных, полученных из базы данных Firebird. Для сравнительного анализа я создал в базе данных 100000 записей с различными типами данных:

• целые числа;
• вещественные числа;
• даты;
• строки;
• изображения.

Сравнение


  1. Загрузка данных
    DataSet LoadFromDataSet Ручная Среднее
    TJvMemoryData 7,7846 сек 5,7500 сек 6,7673 сек
    TMemTableEh 4,5114 сек 7,2978 сек 5,9046 сек
    TdxMemData 6,3804 сек 6,5082 сек 6,4443 сек
    TkbmMemTable 5,4474 сек 6,0562 сек 5,7518 сек
    Загрузки для каждого DataSet'a производились по 5 раз, из полученных значений подсчитвывалось среднее арифметическое. Среднее значение — это среднее арифметическое значения загрузки при помощи метода LoadFromDataSet и значения ручной загрузки.

    Результаты:

    TMemTableEh — самая быстрая загрузка данных при помощи метода LoadFromDataSet
    TJvMemoryData — самая быстрая ручная загрузка данных
    TkbmMemTable — самая быстрая средняя загрузка данных

    Исходный код замера времени
    var
      start_time, end_time, total_time: double;
    
    start_time := GetTickCount;
    ...
    end_time   := GetTickCount;
    total_time := (end_time - start_time) / 1000;

    Исходный код загрузки данных LoadFromDataSet
    //TJvMemoryData
    //function LoadFromDataSet(Source: TDataSet; RecordCount: Integer; Mode: TLoadMode; DisableAllControls: Boolean = True): Integer;
    JvMemoryData.LoadFromDataSet(FIBDataSet, -1, lmCopy);
    
    //TMemTableEh
    //function LoadFromDataSet(Source: TDataSet; RecordCount: Integer; Mode: TLoadMode; UseCachedUpdates: Boolean): Integer;
    MemTableEh.LoadFromDataSet(FIBDataSet, -1, lmCopy, true);
    
    //TdxMemData
    //procedure LoadFromDataSet(DataSet : TDataSet);
    dxMemData.LoadFromDataSet(FIBDataSet);
    
    //TkbmMemTable
    //procedure TkbmCustomMemTable.LoadFromDataSet(Source:TDataSet; CopyOptions:TkbmMemTableCopyTableOptions; Mapping:string='');
    kbmMemTable.LoadFromDataSet(FIBDataSet, [mtcpoAppend]);

    Исходный код ручной загрузки
    while not FIBDataSet.Eof do
    begin
      table.Append;
      table.FieldByName('ID').AsInteger     := FIBDataSet.FieldByName('ID').AsInteger;
      table.FieldByName('SUMM').AsFloat     := FIBDataSet.FieldByName('SUMM').AsFloat;
      table.FieldByName('COMMENT').AsString := FIBDataSet.FieldByName('COMMENT').AsString;
      ...
      table.Post;
    
      FIBDataSet.Next;
    end;
    
    table.First;

    TJvMemoryData — единственный DataSet, у которого ручная загрузка данных оказалось быстрее, чем загрузка при помощи метода LoadFromDataSet.

    TdxMemData — единственный DataSet, который после загрузки данных при помощи метода LoadFromDataSet не возвращает позицию в DataSet на первую запись.
  2. Сортировка
    DataSet Целое число Вещественное число Строка Среднее
    TJvMemoryData 0,3492 сек 0,8330 сек 1,9938 сек 1,0587 сек
    TMemTableEh 0,9014 сек 0,8642 сек 3,6876 сек 1,8177 сек
    TdxMemData 0,3616 сек 0,3650 сек 0,9134 сек 0,5467 сек
    TkbmMemTable 0,1996 сек 0,2186 сек 0,7550 сек 0,3897 сек
    Сортировка для каждого DataSet'a производились по 5 раз, из полученных значений подсчитвывалось среднее арифметическое. Среднее значение — это среднее арифметическое значений сортировок.

    Результаты:

    TkbmMemTable — самая быстрая сортировка целых чисел
    TkbmMemTable — самая быстрая сортировка вещественных чисел
    TkbmMemTable — самая быстрая сортировка строк
    TkbmMemTable — самая быстрая средняя сортировка

    Исходный код замера времени
    var
      start_time, end_time, total_time: double;
    
    start_time := GetTickCount;
    ...
    end_time   := GetTickCount;
    total_time := (end_time - start_time) / 1000;

    Исходный код сортировки данных
    //TJvMemoryData
    //procedure SortOnFields(const FieldNames: string = ''; CaseInsensitive: Boolean = True; Descending: Boolean = False);
    JvMemoryData.SortOnFields(fields, false, false);
    
    //TMemTableEh
    //procedure SortByFields(const SortByStr: string);
    MemTableEh.SortByFields(fields);
    
    //TdxMemData
    dxMemData.SortedField := fields;
    
    //TkbmMemTable
    //procedure SortOn(const FieldNames:string; Options:TkbmMemTableCompareOptions);
    kbmMemTable.SortOn(fields, []);


Отмечу, что только для DataSet'а TMemTableEh при сортировке по нескольким полям можно задавать различное направление сортировки для каждого поля (по возрастанию/по убыванию).

Заключение


Безоговорочным лидером оказался DataSet TkbmMemTable, но и все другие DataSet'ы показали хорошие результаты. Но воспользоваться TkbmMemTable можно только с Delphi XE2.

Конечно, на реальных данных вряд ли понадобиться загружать 100000 записей для отображения пользователю. Так же при выборе DataSet'а нужно учитывать, что на другом наборе данных (например, при отсутствии загрузки изображений или для x64-приложения) результаты скорости работы могут оказаться другими. К тому же многие DataSet'ы имеют дополнительный функционал для работы с гридом из своей библиотеки.

Дополнительно о TMemTableEh


Компонент TMemTableEh появился в версии EhLib 4.0. Все компоненты библиотеки, включая TMemTableEh, работают начиная с Delphi 7.

Преимущества использования MemTableEh по сравнению с другими DataSet'ми следующие:

  1. Поддерживает специальный интерфейс, позволяющий компоненту DBGridEh просматривать все данные, не перемещая активную запись.
  2. Позволяет закачивать в себя данные из объекта TDataDriverEh (свойство DataDriver).
  3. Позволяет выгружать изменения обратно в DataDriver, оперативно или отложено (в зависимости то свойства CachedUpdates).
  4. Позволяет создавать мастер/дитэил отношения на клиенте (фильтруя записи) или на внешнем источнике (обновляя параметры [Params] и перезапрашивая данные c DetailDataDriver'а).
  5. Позволяет сортировать данные, включая Calculated и Lookup поля.
  6. Позволяет создавать и заполнять данные в design-time и сохранять данные в dfm-файле формы.
  7. Позволяет хранить записи в виде дерева. Каждая запись может иметь записи узлы/ветви и сама являться узлом другой родительской записи. Компонент TDBGridEh поддерживает функциональность отображения древовидной структуры этих записей.
  8. Позволяет подключиться к внутреннему массиву другого компонента TMemTableEh (свойство ExternalMemData) и работать с его данными: сортировать, фильтровать, редактировать.
  9. Имеет интерфейс для получения списка всех значений столбца, игнорируя локальный фильтр DataSet'а. TDBGridEh использует это свойство для автоматического формирования списка в выпадающем DropDownBox'е фильтра.

О TRxMemoryData


Разработчки: RxLib
Компонент TRxMemoryData появился в версии RxLib 2.60.

DataSet TRxMemoryData не участвует в сравнении, потому что в 2002 году библиотека RxLib была официально включена в состав JVCL. В JVCL есть утилита для быстрой замены всех Rx компонентов, функций и юнитов на JVCL-версии.

Почему стоит перейти на JVCL:

В отличие от RxLib, JVCL развивается. Ошибки исправляются. Регулярно выходят версии с поддержкой новых версий Delphi. JVCL компоненты поддерживают новые версии Windows и Windows-стили.

Порядок использования DataSet'ов в моей практике


  1. TRxMemoryData
  2. TMemTableEh
  3. TRxMemoryData
  4. TJvMemoryData
  5. TkbmMemTable

Комментарии (33)


  1. pda0
    09.03.2018 14:20

    Код ручной загрузки доставляет. :) Во первых вокруг цикла по DataSet источника надо делать DisableControls/EnableControls, потому что это отключает целую кучу внутренних событий, которые для копирования не нужны. Во вторых, если в TJvMemoryData нет вычислимых полей, то стоит установить AutoCalcFields в False. В третьих, дёргать FieldByName внутри цикла — преступление против производительности. Необходимо задать локальные переменные типов TIntegerField, TFolatField и TStringField, инициировать их FieldByName до цикла для обоих DataSet'ов, а внутри уже использовать их.

    Потому что 4 секунды на 100к строк — это не оч. :)

    P.S. Ну и всегда остаётся путь хака. Вломиться во внутренности DataSet в памяти и воссоздать его структуру вручную. Покопался в загашнике и нашёл старый класс для копирования данных из TADODataset в TJvMemoryData. По идее, если не задавать DEFINE ADODIRECT он должен элементарно переделываться на любой входной DataSet. Правда я не уверен, заработает ли он в современной Delphi, но было бы забавно увидеть его результаты в табличке с обновлённым сравнением. :)


    1. financial_decisions Автор
      09.03.2018 14:30

      DisableControls/EnableControls — это конечно нужно выставлять, но в моем примере никаких дополнительных обработчиков событий на DataSet'ы не повешено.
      Так же есть удобный метод AppendRecord, позволяющий добавить сразу строку, а не вставлять поштучно по ячейке.


      1. pda0
        09.03.2018 14:37

        Надо вспоминать, но там по моему всё равно внутренности проворачиваются, даже если ничего не навешано.


  1. ElectroGuard
    09.03.2018 14:45

    Спасибо за статью, но как же 'стандартный' TClientDataSet?


    1. financial_decisions Автор
      09.03.2018 14:50

      TClientDataSet отсутствует в бесплатной версии Delphi 10.2 Tokyo Starter


      1. Alert123
        09.03.2018 17:25

        Но вроде и те компоненты что вы сравниваете — не все бесплатные, зачем ограничиваться бесплатной версией Delphi и отказываться от стандартного TClientDataSet?
        Кроме того можно было бы добавить к сравнению и Sqlite с режимом :memory:
        Ну и насчет сравнения скорости загрузки — когда то эксперементально выяснил что много времени тратится на само чтение с HDD, так что чтение данных предварительно сжатых zstd с последующим расжатием в памяти выходит быстрее чем просто загрузка несжатых.


  1. kapas19
    09.03.2018 17:26

    А почему в обзоре отсутствует бесплатный SQLMemTable (http://www.aidaim.com)?


    1. financial_decisions Автор
      09.03.2018 17:26

      Обязательно посмотрю этот DataSet


    1. financial_decisions Автор
      09.03.2018 18:31

      Посмотрел этот DataSet
      1. Метод LoadFromDataSet отсутствует
      2. Ручная загрузка данных около 6,8 сек
      3. Метода для сортировки данных нет

      Судя по названию, все-таки этот DataSet предназначен для подключения к базе данных


      1. kapas19
        09.03.2018 19:08

        > Судя по названию, все-таки этот DataSet предназначен для подключения к базе данных

        По сути, это набор компонент, обеспечивающих создание приложений с использованием базы данных в памяти (in-memory) с поддержкой SQL.

        SQLMemTable состоит из нескольких компонент:
        1. TSQLMemTable
        2. TSQLMemQuery
        3. TSQLMemBatchMove
        и TSQLMemDatabase
        Доступ к данным в SQLMemTable осуществляется с использованием SQL-запросов.
        По производительности спорить не буду.


  1. HSerg
    09.03.2018 19:17

    У kbmMemTable тоже есть возможность сортировки по нескольким полям с ASC/DESC. Пример есть в EhLibKbmMT.pas.


    1. financial_decisions Автор
      09.03.2018 19:57

      Благодарю, посмотрю. TRxMemoryData тоже умеет сортировать по нескольким полям с ASC/DESC


  1. vsapronov
    10.03.2018 06:01

    Захожу в 2018 на GT, а тут Delphi. Оно еще живо? Все еще на паскале?


    1. andyshark1974
      10.03.2018 08:14

      А почему нет?


      1. Мегатонны готового кода. Выбрасывать и жалко и не имеет смысла — оно же работает (1-й принцип программиста).
      2. Привычный интерфейс. Я пытался себя поломать несколько раз на другие системы — бросил. Короткие клавиши Дельфи уже в подкорке сидят.
      3. Мультиплатформенность. Изучать Eclipse, Jav-у и прочее есть свмысл для молодых вперед-прущих. В силу возраста пересматриваешь взгляды на программирование и выбираешь малозатратные системы. А найти систем у которая пишет на Win, Mac-OS и Android — как-то таких мало.
      4. Скорость разработки. Тут если рука набита на системе — смысла нет ее менять.

      Спорить по поводу достоинств тех или иных систем смысла не вижу. Если эта работает, стоит вменяемо, дает даже некоторые преимущества перед другими системами — ну нафиг от нее отказываться? В угоду Си или Джаве? Не вижу смысла.


      P.S. Я в свои системы параллельно ввожу Питон и Пых. Что-то на серверных обработках, что-то прям локально (Питон в Дельфю встраивается). Сам Object Pascal сильно доработан, много новых вещей добавлено и т.д. и т.п.


      1. vsapronov
        11.03.2018 01:12
        -1

        Вот мне кажется, что ваш комментарий — это сам по себе ответ на НЕ заданный (мной уж точно) вопрос: «надо ли отказываться от Delphi?» Любой современный программист прочитав ваш комментарий сразу хорошо поймет, почему надо отказываться. Вы как бы живой пример того, что может случиться если продолжать держаться за Delphi мертвой хваткой. Я приведу несколько маркеров из вашего текста:

        1. «Привычный интерфейс». Уже давно ценится гибкость и умение осваивать новые технологии — не то чтобы просто IDE-шечку какую-то. Если кто-то хочет лишить себя чуть ли не главного требуемого в профессии навыка (быстрой обучаемости), тогда надо Delphi.
        2. «Изучать Eclipse, Jav-у и прочее есть свмысл для молодых вперед-прущих. » Здесь рукалицо показали не только «молодые», а даже те, кому за 30. Java? Для тех кто «моложе»? Это результат какой-то пропаганды в Delphi-сообществе? Молодые — это функциональщина: Scala, Kotlin, F#. Молодые — это скорость нативных языков: Go, Rust, Swift. Если хотите думать, что Java — это про молодых, то займитесь Delphi. А, ну и по деталям: Eclipse? Как бы уже пересели на IDEA и даже Google отказался от говенного Eclipse. Но, конечно, если вы провели в коме Delphi последнее десятилетие, то можете этого и не знать. Еще один повод для Delphi: хотите сузить кругозор, ну вы поняли...
        3. Сервера на Python и PHP — тоже нормально. Прямо повеяло современностью и грамотными масштабируемыми решениями.
        4. Утверждать, что Object Pascal «доработан» можно только упоминая до чего конкретно он доработан. Pascal был и остается одним из самых многословных языков с отсутствием многих современных фич. Опять же на заметку — хотите писать тонны кода, когда можно их не писать — берите Delphi.


        1. chromimon
          11.03.2018 11:04

          «Привычный интерфейс»

          чуть ли не главного требуемого в профессии навыка (быстрой обучаемости)


          Как ваш комментарий связал IDE и обучаемость?

          Уж что, а что качественных IDE — чуть ли не меньше 10 штук на весь мир (семейство IDEA — по суди за 1 шт. с вариациями) и ситуация сохраняется годами, каждый месяц не выходят принципиально новых.


    1. ITMatika
      10.03.2018 11:26

      Вы не поверите!


    1. financial_decisions Автор
      10.03.2018 18:46
      +2

      В 2028 году тоже будет Delphi. От него никуда не деться. Языки программирования приходят и уходят, а Delphi остается


      1. vsapronov
        11.03.2018 00:48

        Давайте зарубимся. В 2028 под комментарием «Delphi жив» не наберется +10.


    1. ElectroGuard
      11.03.2018 00:09

      20 лет уж хоронят. И еще лет 20 будут.


    1. quwy
      11.03.2018 01:39

      Видите ли, не всем по душе текущее скриптовое безумие. Делфя на данный момент является самой мощной средой разработки десктопного софта. Очень много разных библиотек/GUI-компонентов и нейтивный бинарник без каких-либо зависимостей на выхлопе. Много современного хипстерского шлака может похвастаться подобным?


      1. vsapronov
        11.03.2018 01:44

        C# и WPF — это скриптовое безумие? Это если мы про десктопный софт.
        И еще вот вопрос: мы знаем много софта существующих сразу на нескольких платформах: Windows, MacOS, iOS, Android. Какие из них написаны на Delphi?


        1. InChaos
          12.03.2018 13:45
          +1

          C# это еще то приключение, когда надо несколько сторонних компонент прицепить, а они все в разных версиях .NET. И даже когда в исходниках, не сразу поймешь где еще сцеплены и на что завязаны, где то манифесты кривые. А сколько глюков случается в самой Visual Studio. Причем часто настолько непонятные, что даже в msdn-е советуют переустановить. Согласен еще возможно опыта маловато, и не умею готовить, но когда на профильных форумах ищешь решение возникшей проблемы, приводишь кусок кода, и оказывается, что у кого-то их нет в приведенном тексте, а у кого-то совсем другая ошибка и совсем в другом месте, то явно что то не то.


        1. quwy
          12.03.2018 16:04

          C# и WPF — это скриптовое безумие? Это если мы про десктопный софт.

          По количеству компонентов не сравнить. Да и свистопляска с версиями рантайма (как и сама необходимость его установки) очень не способствует.

          И еще вот вопрос: мы знаем много софта существующих сразу на нескольких платформах: Windows, MacOS, iOS, Android.

          Тут как правило два варианта:
          1. Это скриптовое безумие типа электрона, от которого нормальные люди стараются держаться подальше.
          2. Это независимые приложения, созданные в разных средах и имеющие крайне ограниченную разделяемую кодовую базу.

          Вы можете назвать среду разработки, которая с одинаковым успехом создавала бы нейтивные приложения одновременно под Windows, MacOS, iOS и Android? И много популярных приложений в ней создано?

          Какие из них написаны на Delphi?

          Я за этим не слежу, да и пофиг как-то. Среда есть, функции свои (хоть и не без проблем) выполняет, и ею пользуются. А если джаваскриптовым хипстерам это кажется не модным, то почему это должно парить тех, кто просто делает свою работу удобным инструментом?


  1. qw1
    10.03.2018 20:24

    Безоговорочным лидером оказался DataSet TkbmMemTable, но и все другие DataSet'ы показали хорошие результаты. Но воспользоваться TkbmMemTable можно только с Delphi XE2.

    У меня в старых проектах на Delphi 7 использовался.
    Новые версии не поддерживают D7, но
    As subscriber, access to older versions supporting D5 to D2007 and FPC is available.


    1. financial_decisions Автор
      10.03.2018 20:27

      Какой версией TkbmMemTable можно пользоваться в Delphi 7?


      1. qw1
        10.03.2018 20:51

        У меня в проекте валяется kbmMemTable v. 5.52


  1. DrPass
    11.03.2018 00:41

    Из данных, полученных в статье, можно сделать вывод, что разницей в быстродействии между датасетами в подавляющем большинстве случаев можно пренебречь.


    1. financial_decisions Автор
      11.03.2018 10:42

      Нельзя таким пренебрегать. Машина, на которой происходило сравнение, достаточно мощная и жесткий диск SSD


      1. InChaos
        12.03.2018 13:51

        Не понимаю зачем грузить сотни тысяч строк? Все фильтрации, условия, выборки должен делать сервер. А то пока вы их выберете, даже за 3 сек, они запросто уже на сервере изменятся. И прощая один из столпов баз данных — целостность. Если пользователь хочет видеть даже 10000 строк, явно он что то не понимает, и потом будет делать агрегацию, фильтрацию где нить в экселе. Так ему надо объяснить что можно это сделать самим SQL сервером, и он получит уже готовые данные, в нужном объеме 50-100-200 строк.


  1. uterr
    11.03.2018 10:20
    +1

    Простите за вопрос, но почему не на habrahabr?
    Я совсем не против, чтобы здесь, но на хабре, в первую очередь, больше просмотров и больше аудитория
    я только за, чтобы два ресурса слились обратно, но для этого нужно и остальных уговорить :)


    1. financial_decisions Автор
      11.03.2018 10:31

      Мою первую статью модераторы почему-то перенесли с habrahabr на geektimes. Тут и остался.
      А просмотров здесь тоже очень много.


  1. MinamotoSoft
    11.03.2018 14:05

    Мьі тут чето потестили. Результат не понрсвился, потому похезали. Читайте — наслаждайтеси.