TStringList объектов, занимающих тонны памяти в Delphi XE

Я работаю над программой моделирования.

первое, что делает программа, - это чтение в огромном файле (28 Мб, около 79 000 строк), разбор каждой строки (около 150 полей), создание класса для объекта и добавление его в TStringList.

Он также читает в другой файл, который добавляет больше объектов во время выполнения. В конце концов, это около 85 000 объектов.

Я работал с Delphi 2007, и программа использовала много памяти, но она работала нормально. Я обновлен до Delphi XE и перенастроил программу, и теперь она использует намного больше памяти, и в конечном итоге она заканчивается на середине запуска.

Итак, в Delphi 2007 он будет использовать 1.4 гигабайта после чтения в исходном файле, что, очевидно, огромное количество, но в XE он заканчивается использованием почти 1.8 гигабайтов, что действительно огромно и приводит к выбегу и получению ошибки

У меня вопрос

  1. почему он использует так много памяти?
  2. почему он использует так много памяти в XE, чем 2007?
  3. что я могу с этим поделать? Я не могу изменить размер или длину файла, и мне нужно создать объект для каждой строки и сохранить его где-нибудь

спасибо

10 ответов


трудно сказать, почему ваш файл 28 МБ расширяется до 1,4 ГБ объектов, когда вы анализируете его на объекты, не видя кода и объявлений классов. Кроме того, вы говорите, что храните его в TStringList вместо TList или TObjecList. Это звучит так, как будто вы используете его как своего рода отображение string->object key/value. Если это так, вы можете посмотреть на TDictionary класс Generics.Collections блок в XE.

что касается того, почему вы используете больше памяти в XE, это потому, что string тип изменен со строки ANSI на строку UTF-16 в Delphi 2009. Если вам не нужен Unicode, вы можете использовать TDictionary для экономии места.

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

  • прочитайте файл в TStringList. (Это будет использовать примерно столько же памяти, сколько размер файла. Может быть, в два раза больше, если он будет преобразован в строки Unicode.) Не создавайте объекты данных.
  • когда вам нужен конкретный объект данных, вызовите процедуру, которая проверяет список строк и ищет строковый ключ для этого объекта.
  • проверьте, имеет ли эта строка связанный с ней объект. Если нет, создайте объект из строки и свяжите его со строкой в TStringList.
  • возвращает объект, связанный со строкой.

это сохранит использование вашей памяти и время загрузки, но это только полезно, если вам не нужны все (или большой процент) объектов сразу после загрузки.


только одна идея, которая может сэкономить память.

вы можете оставить данные в исходных файлах, а затем просто указать на них из структур в памяти.

например, это то, что мы делаем для просмотр больших файлов журналов почти мгновенно: мы запоминаем-сопоставляем содержимое файла журнала, затем быстро анализируем его для создания индексов полезной информации в памяти, затем читаем содержимое динамически. Во время чтения строка не создается. Только указатели к каждой строке, начинающейся с динамических массивов, содержащих необходимые индексы. Зову TStringList.LoadFromFile будет определенно намного медленнее и потреблять память.

код здесь - см. TSynLogFile класса. Фокус в том, чтобы прочитать файл только один раз и сделать все индексы на лету.

например, вот как мы получаем строку текста из содержимого файла UTF-8:

function TMemoryMapText.GetString(aIndex: integer): string;
begin
  if (self=nil) or (cardinal(aIndex)>=cardinal(fCount)) then
    result := '' else
    result := UTF8DecodeToString(fLines[aIndex],GetLineSize(fLines[aIndex],fMapEnd));
end;

мы используем тот же трюк, чтобы разбор содержимого JSON. Используется такой смешанный подход по самым быстрым библиотекам доступа XML.

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

таким образом, у вас больше не будет дублированной структуры, только логика в ОЗУ и данные о файлах с отображением памяти - я добавил "s" здесь, потому что одна и та же логика может отлично сопоставляться с несколькими исходными файлами данных (вам нужно некоторое "слияние" и "живое обновление" AFAIK).


  • в Delphi 2007 (и более ранних версиях) строка является строкой Ansi, то есть каждый символ занимает 1 байт памяти.

  • в Delphi 2009 (и более поздних версиях) строка является строкой Юникода, то есть каждый символ занимает 2 байта памяти.

AFAIK, нет никакого способа сделать Delphi 2009+ TStringList объект использует строки Ansi. Вы действительно используете любую из функций TStringList? Если нет, вы можете использовать массив строк вместо.

тогда, естественно, вы можете выбрать между

type
  TAnsiStringArray = array of AnsiString;
  // or
  TUnicodeStringArray = array of string; // In Delphi 2009+, 
                                         // string = UnicodeString

чтение хотя комментарии, похоже, вам нужно поднять данные из Delphi и в базу данных.

оттуда легко сопоставить доноров органов с приемниками*)

SELECT pw.* FROM patients_waiting pw
INNER JOIN organs_available oa ON (pw.bloodtype = oa.bloodtype) 
                              AND (pw.tissuetype = oa.tissuetype)
                              AND (pw.organ_needed = oa.organ_offered)
WHERE oa.id = '15484'

Если вы хотите увидеть пациентов, которые могут соответствовать новым донорам органов 15484.

в памяти вы обрабатываете только несколько пациентов, которые соответствуют.

*) упрощается до неузнаваемости, но все же.


В дополнение к посту Андреаса:

до Delphi 2009 заголовок строки занимал 8 байт. Начиная с Delphi 2009, заголовок строки занимает 12 байт. Таким образом, каждая уникальная строка использует на 4 байта больше, чем раньше, + тот факт, что каждый символ занимает в два раза больше памяти.

кроме того, начиная с Delphi 2010, я считаю, что TObject начал использовать 8 байтов вместо 4. Поэтому для каждого отдельного объекта, созданного delphi, delphi теперь использует еще 4 байта. Эти 4 байта были добавлены для поддержки класс TMonitor, я полагаю.

Если вам отчаянно нужно сохранить память, вот небольшой трюк, который может помочь, если у вас есть много строкового значения, которое повторяется.

var
  uUniqueStrings : TStringList;

function ReduceStringMemory(const S : String) : string;
var idx : Integer;
begin
  if not uUniqueStrings.Find(S, idx) then
    idx := uUniqueStrings.Add(S);

  Result := uUniqueStrings[idx]
end;

обратите внимание, что это поможет только если у вас есть много строковых значений, которые повторяются. Например, этот код использует 150mb меньше в моей системе.

var sl : TStringList;
  I: Integer;
begin
  sl := TStringList.Create;
  try
    for I := 0 to 5000000 do
      sl.Add(ReduceStringMemory(StringOfChar('A',5)));every
  finally
    sl.Free;
  end;
end;

Я также прочитал много строк в моей программе, которые могут приблизиться к паре ГБ для больших файлов.

Не дожидаясь 64-битного XE2, вот одна идея, которая может вам помочь:

Я обнаружил, что хранение отдельных строк в stringlist будет медленным и расточительным с точки зрения памяти. Я закончил тем, что заблокировал струны вместе. Мой входной файл имеет логические записи, которые могут содержать от 5 до 100 строк. Поэтому вместо хранения каждой строки в stringlist я сохраняю каждую запись. Обработка записи, чтобы найти нужную строку, добавляет очень мало времени к моей обработке, поэтому это возможно для меня.

Если у вас нет логических записей, вы можете просто выбрать размер блокировки и сохранить все (скажем) 10 или 100 строк вместе как одну строку (с разделителем, разделяющим их).

другая альтернатива-хранить их в быстром и эффективном файле на диске. Я бы порекомендовал это открытый источник Synopse Большой Стол by Арно Бушез.


могу ли я предложить вам попробовать использовать класс tansistringlist библиотеки классов jedi (JCL), который похож на TStringList fromDelphi 2007 в том, что он состоит из AnsiStrings.

даже тогда, как упоминали другие, XE будет использовать больше памяти, чем delphi 2007.

Я действительно не вижу значения загрузки полного текста гигантского плоского файла в stringlist. Другие предложили подход bigtable, такой как Arnaud Bouchez, или использование SqLite, или что-то в этом роде, и я с ними согласен.

Я думаю, вы также можете написать простой класс, который загрузит весь файл, который у вас есть в память, и предоставит способ добавить ссылки на объекты по строкам в гигантский буфер ansichar в памяти.


начиная с Delphi 2009, не только строки, но и каждый TObject удвоился в размере. (См.почему размер TObject удвоился в Delphi 2009?). Но это не объяснило бы такое увеличение, если бы было только 85 000 объектов. Только если эти объекты содержат много вложенных объектов, их размер может быть соответствующей частью использования памяти.


в вашем списке много повторяющихся строк? Возможно, попытка сохранить только уникальные строки поможет уменьшить размер памяти. См. мой вопрос о пуле строк для возможного (но, возможно, слишком простого) ответа.


вы уверены, что не пострадают от случая fragementation памяти?

обязательно используйте последнюю версию FastMM (в настоящее время 4.97), то взгляните на!--5-->UsageTrackerDemo демонстрация, содержащая форму карты памяти, показывающую фактическое использование памяти Delphi.

наконец-то взгляните на VMMap это показывает вам, как используется ваша память процесса.