Насколько заметна разница в производительности между TList, TObjectList и обычным массивом, если ее можно оценить? - PullRequest
8 голосов
/ 18 марта 2011

* Суммирование:

Пожалуйста, ознакомьтесь со знающими комментариями экспертов Delphi. Специально для меня я бы попытался использовать старый TList / TObjectList, как предложил Дэвид, и использовать hard-cast и свойство TObjectList.List, как предложил А.Бучез. Я буду пробовать TDynArray при рефакторинге в будущем.

=============================================== ======================

Скажите, что у меня есть класс TAtom, как определено в следующем коде. Во время выполнения существует от hundreds до thousands экземпляров TAtom, stored in a dynamic array на данный момент. Во время выполнения мне нужно выполнить простую математику с плавающей точкой на TAtom.X/Y/Z всех существующих экземпляров TAtom более чем 30 раз в секунду.

Теперь мне нужно добавить способность adding, inserting, deleting к экземплярам TAtom во время выполнения. Кажется, что я могу выбрать (1) запросить большой массив; (2) придерживаться динамического массива и вручную SetLength; (3) переключиться на обычный TList; (4) переключиться на обычный TObjectList.

Я хочу избегать (1), если в этом нет необходимости, потому что тогда мне приходится довольно много менять сигнатуры функций. (2) выглядит не очень хорошо, потому что TList / TObjectList кажется рожденным для этой задачи. Однако, поскольку приведение типов необходимо с использованием обычного TList / TObjectList, может кто-нибудь прокомментировать возможное снижение производительности? Я имею в виду, что было бы лучше, если бы бремя производительности можно было оценить до того, как я переписал код. Если производительность заметно упадет, есть ли другая техника, которую я мог бы использовать?

Кроме того, мне интересно, есть ли разница в производительности между использованием TList и TObjectList?

  TAtom = class
  public
    ElementZ: Integer;
    X, Y, Z: Extended;  
    other variables: other types;
  end;

  TAAtom = array of TAtom;

Ответы [ 8 ]

7 голосов
/ 18 марта 2011

Могу ли я добавить другой выбор в ваш список?

Если вы не используете какую-либо функцию наследования для данных в TAtom, вы можете использовать record вместо class. Каждый экземпляр класса должен быть размещен в памяти, заполнен нулями и инициализирован индивидуально. Getmem/Freemem всегда стоит, а фрагментация памяти увеличится.

Предварительно выделенная динамическая array of record будет быстрее, чем отдельные экземпляры классов для добавления. И данные лучше подойдут для кэша CPU L1 / L2.

Для вставки и удаления массив таких записей будет медленнее, чем TList, если у вас огромное количество элементов, потому что будет больше данных для удаления / вставки (TList/TObjectList оба поддерживают только список указатели). Для еще более быстрой вставки / удаления лучше использовать связанный список.

Из-за внутреннего уведомления в механизме TList/TObjectList есть некоторые издержки. механизм И свойство GetItem() может быть немного медленнее (из-за проверки диапазона), чем напрямую использовать динамический массив.

Но с нашей оболочкой TDynArray вы можете придерживаться динамического массива и при этом иметь хорошую производительность, функции предварительного выделения и TList -подобные методы. И даже больше доступных методов, таких как SaveToStream, Slice, Reverse, сортировка по внешним индексам и тому подобное ...

type
  TAtom = record // could be 'packed record' to save memory (but loose perf)
    ElementZ: Integer;
    X, Y, Z: Extended;  
    other variables: other types;
    // TDynArray also handle complex (e.g. string) types here
  end;
  TAtoms = array of TAtom;

var Atom: TAtom;
    AtomArray: TAtoms;
    AtomCount: integer;
    Atoms: TDynArray;
begin
  Atoms.Init(TypeInfo(TAtoms),AtomArray,@AtomCount);
  Atoms.Capacity := 10000; // pre-allocate array = same as SetLength(AtomArray,10000)
  for i := 1 to 10000 do begin
    A.ElementZ := Random(1000);
    A.X := Random;
    A.Y := Ramdom;
    A.Z := Random;
    // set other fields
    Atoms.Add(A); // fast adding of A properties
  end;
  // you have TList-like methods for your dynamic array
  Atoms.Delete(500); // delete 500th item
  A.ElementZ := 5000;
  Atoms.Insert(500,A); // insert A values at 500th index
  assert(Atoms.Count=10000);
  assert(AtomCount=10000); // same as Atoms.Count
  Atoms.Compare := SortDynArrayInteger;
  Atoms.Sort; // will sort by 1st Integer value = ElementZ
  for i := 1 to Atoms.Count-1 do // or AtomCount-1
    // you have still direct access to AtomArray[]
    // -> this is even the fastest access to the data
    assert(AtomArray[i].ElementZ >=AtomArray[i-1].ElementZ )
  Atoms.SaveToStream(aStream); // will also save any string content
  Atoms.Reverse; // reverse all items order
  Atoms.Clear;
  // faster adding will be done with direct access to the dynamic array
  Atom.Count := 10000; // allocate memory for 10000 items
  for i := 0 to 10000-1 do
  with AtomArray[i] do
  begin
    ElementZ := Random(2000);
    X := Random;
    Y := Random;
    Z := Random;
  end;
  Atoms.Sort; // TDynArray knows about the data just created
end; // no need to have any try...finally ..Free block

Работает с Delphi 6 до XE.

С новой версией Delphi, поддерживающей дженерики, вам лучше пойти в этом направлении.

6 голосов
/ 18 марта 2011

Если вы используете Generics.Collections.TObjectList<TAtom> и нет необходимости в приведении.

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

Пока вы избегаете SetLength(A, Length(A)+1) и выбираете более разумную стратегию размещения, динамические массивы эквивалентныко всем TList подобным классам.

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

Это все несколько умозрительно, и вам действительно нужно измерять - иначе мы можем только догадываться.

3 голосов
/ 18 марта 2011

Однако, потому что приведение типов необходимо с помощью обычного TList / TObjectList, может кто-нибудь прокомментировать возможное исполнение хит?

Если вы наберете приведение с использованием формы

List[I] as TAtom

, так как будут добавлены небольшие накладные расходы, которые действительно могут добавить в вашей ситуации. Тем не менее, если вы тяжело Typecast

TAtom(List[I])

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

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

1 голос
/ 18 марта 2011

Динамический массив записей будет иметь наименьшее влияние при доступе к элементам, если ваши атомы являются объектами, то все списки будут в некоторой степени эквивалентны с точки зрения скорости доступа.

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

  • связанный список, если вам не нужен доступ к атомам по индексу
  • дерева, если у вас есть возможность использоватьпространство разделов ваших атомов, вы также можете использовать этот раздел для хранения ваших атомов, а не массива
  • , разрешающего неопределенные / nil элементы в вашем массиве / списке, а также поддержание стека неопределенных / nil элементов ииндексировать, если вам нужен отсортированный список (возможно, решение с наивысшей производительностью, но также, вероятно, наиболее сложное в плане эффективности)
1 голос
/ 18 марта 2011

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

Одним из критериев может быть отношение чтения / обновления к последовательности.Если последовательность обновляется нечасто после создания, то должна быть ощутимо лучшая скорость с dynarrays, потому что доступ к элементам с TList и подобным требует одного вызова метода плюс проверка границ, плюс проверка типа, если вы используете оператор as.

В конце концов, стоимость арифметики, выполняемой на TAtom, должна доминировать во время выполнения, делая выбор dynarray или TListXXX неактуальным.

1 голос
/ 18 марта 2011

Первый вопрос: мы говорим о Classes.TList и Contnrs.TObjectList или мы говорим о Generics.Collections.TList соответственно Generics.Collections.TObjectList?

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


Если мы говорим о «старшем» TList иTObjectList, тогда нам нужно сравнить только TList с эквивалентом динамического массива, поскольку TObjectList является потомком TList, поэтому он наследует все свои характеристики производительности.TList использует блок памяти, выделенный с помощью ReallocMem.Динамический массив делает то же самое внутри, поэтому не должно быть существенной разницы!

Заключение

Если между этими двумя показателями есть разница в производительности, это, вероятно, потому что наивное использование динамического массива используетстрашный SetLength(A, Length(A)+1), в то время как лучшая реализация во всех предоставленных Delphi контейнерах предварительно выделяет память большими кусками.При правильном коде не должно быть существенной разницы между этими альтернативами!

1 голос
/ 18 марта 2011

Поскольку TObjectList является прямым потомком TList, производительность будет очень близка.

1 голос
/ 18 марта 2011

Создайте тестовый проект и измерьте время добавления, вставки и удаления тысяч экземпляров TAtom, используя четыре метода. Затем решите, какой из них использовать.

TList и TObjectList, вероятно, быстрее, чем динамический массив при добавлении, вставке и удалении, потому что динамический массив должен постоянно перераспределяться. Реализация TList и TObjectList этого не делает.

...