TStringList объектов, занимающих тонны памяти в Delphi XE - PullRequest
6 голосов
/ 25 августа 2011

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

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

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

Я работал с Delphi 2007, и программа использовала много памяти, но работала нормально.Я обновил до Delphi XE и перенес программу снова, и теперь она использует ОЧЕНЬ больше памяти, и в итоге она исчерпывает память на полпути.

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

Так что мой вопрос

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

Спасибо

Ответы [ 10 ]

10 голосов
/ 25 августа 2011

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

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

Например, это то, что мы делаем для просмотра больших файлов журнала почти мгновенно : мы отображаем в памяти содержимое файла журнала, затем быстро анализируем его, чтобы создать в памяти индексы полезной информации, затем читаем содержание динамически . Строка не создается во время чтения. Только указатели на начало каждой строки с динамическими массивами, содержащими необходимые индексы. Вызов 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).

6 голосов
/ 25 августа 2011

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

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

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

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

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

3 голосов
/ 25 августа 2011

Читая комментарии, звучит так, будто вам нужно вытащить данные из 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.

В памяти вы обращаетесь только с несколькими подходящими пациентами.

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

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

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

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

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

type
  TAnsiStringArray = array of AnsiString;
  // or
  TUnicodeStringArray = array of string; // In Delphi 2009+, 
                                         // string = UnicodeString
1 голос
/ 25 августа 2011

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

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

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

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

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

1 голос
/ 25 августа 2011

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

До 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;

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

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;
0 голосов
/ 26 августа 2011

Вы уверены, что не страдаете от случая фрагментации памяти?

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

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

0 голосов
/ 26 августа 2011

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

0 голосов
/ 26 августа 2011

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

0 голосов
/ 26 августа 2011

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

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

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

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

...