TClientDataSet использует слишком много памяти для строковых полей - PullRequest
6 голосов
/ 27 марта 2019

Меня заставили задать этот вопрос, когда я пытался поддержать этот вопрос с помощью MCVE.

Недавно я начал замечать, что TClientDataSet быстро исчерпывает память.У меня была проблема с производством, когда он не мог загрузить набор данных примерно с 60 000, что мне показалось на удивление низким.Набор данных клиента был подключен через провайдера с ADODataSet, который нормально загружался.Я запустил этот запрос отдельно и вывел результат в CSV, который дал мне файл размером <30 МБ. </p>

. Поэтому я провел небольшой тест, в котором я могу загрузить до 165 КБ записей в наборе данных клиента, который имеетстроковое поле размером 4000. Фактическое значение поля составляет всего 3 символа, но это не имеет значения для результата.

Похоже, что каждая запись занимает как минимум эти 4000 символов,4000 x 2 байта x 165K записей = 1,3 ГБ, так что начинается ограничение на 32-битный предел памяти.Если я превращу его в заметку, я могу легко добавить 5 миллионов строк.

program ClientDataSetTest;
{$APPTYPE CONSOLE}
uses SysUtils, DB, DBClient;

var
  c: TClientDataSet;
  i: Integer;
begin
  c := TClientDataSet.Create(nil);
  c.FieldDefs.Add('Id', ftInteger);
  c.FieldDefs.Add('Test', ftString, 4000); // Actually claims this much space...
  //c.FieldDefs.Add('Test', ftMemo); // Way more space efficient (and not notably slower)
  //c.FieldDefs.Add('Test', ftMemo, 1); // But specifying size doesn't have any effect.
  c.CreateDataSet;

  try
    i := 0;
    while i < 5000000 do
    begin
      c.Append;
      c['Id'] := i;
      c['Test'] := 'xyz';
      c.Post;

      if (i mod 1000) = 0 then
        WriteLn(i, c['Test']);

      Inc(i);
    end;

  except
    on e: Exception do
    begin
      c.Cancel;
      WriteLn('Error adding row', i);
      Writeln(e.ClassName, ': ', e.Message);
    end;
  end;

  c.SaveToFile('c:\temp\output.xml', dfXML);
  Writeln('Press ''any'' key');
  ReadLn;
end.

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

  • Может ли TClientDataSet быть настроен таким образом, чтобы он обрабатывал это по-другому?Я просмотрел его свойства, но не могу найти ничего похожего на это.
  • Можно ли решить эту проблему, используя другой тип поля?Я думаю о ftMemo, но у него есть некоторые другие недостатки, такие как размер, не используемый для усечения, и некоторые проблемы с отображением, такие как TDBGrid, отображающий его как (MEMO) вместо фактического значения.
  • Есть падение-в заменах TClientDataSet, которые решают эту проблему?Речь идет не только о части в памяти, но и об обмене данными с компонентами ADO через TProvider, что является основным способом, которым я использую его в этом проекте, поэтому ни один набор данных памяти не сработает.

Что касается последнего пункта, я случайно обнаружил этот вопрос , где он скрыт в комментариях, упоминается vgLib, но все, что я нахожу по этому поводу, это неработающие ссылки, и я даже не знаю, если эторешил бы эту проблему.Очевидно, что код C ++ для MidasLib уже доступен, но, поскольку он составляет 1,5 МБ неясного кода, я подумал, что, возможно, стоит спросить здесь, прежде чем углубляться в это.;)

Ответы [ 2 ]

2 голосов
/ 28 марта 2019

всякий раз, когда мне нужно довольно длинное «строковое» поле в CDS, я вместо этого стремлюсь создать мемо-поле.Помимо вышеупомянутой проблемы с отображением (которая может быть решена довольно безболезненно), есть несколько других ограничений, поэтому у меня есть собственный потомок компакт-дисков.формат внутренней строки hyperbase (не vglib) одинаков, поэтому он ничего не изменит в этом отношении.Между прочим, есть dac (например, firedac), позволяющие настраивать и выбирать отображение типа целевого поля.не уверен, что компоненты ado могут быть исправлены / улучшены для достижения аналогичной функциональности.Более того, в наборе данных iirc firedac есть возможность управления внутренней структурой полей строки (встроенный буфер в строке или просто указатель на динамически размещаемый буфер), но он не заменяет компакт-диски 1: 1.

2 голосов
/ 28 марта 2019

Существует разница между тем, как поля больших двоичных объектов (памятка) и обычные поля хранят и получают свои данные. Поля BLOB-объектов не хранят данные в буфере записи (см. TBlobField.GetDataSize) и используют другой набор методов для хранения или извлечения этих данных.

Размер каждой записи возвращается при вызове TField.GetDataSize. Для TStringField это требуемый размер строки + 1.

TCustomClientDataSet.InitBufferPointers использует это как часть своего расчета для значения FRecBufSize, которое используется как объем памяти для выделения каждой записи в TCustomClientDataSet.AllocRecordBuffer.

Итак, чтобы ответить на ваши вопросы:

  • TClientDataSet не может быть настроен для этого по-другому.
  • Это может быть решено другими типами полей, но они должны были бы происходить из TBlobField. Размер буфера распределяется заранее, поэтому обычные поля не могут содержать разные размеры в зависимости от их содержимого.
  • Я не уверен насчет замены. У Dev Express есть dxMemData, но я не знаю, сталкивается ли он с такими же проблемами или это замена с заменой.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...