Насколько заметна разница в производительности между TList, TObjectList и простым массивом, если ее можно оценить?
-
27-10-2019 - |
Вопрос
*< sizesОбобщение :
Ознакомьтесь с комментариями экспертов Delphi. Специально для меня я бы попытался использовать старый TList / TObjectList, как предложил Дэвид, и использовать жесткое приведение и свойство TObjectList.List, как предложил А.Бушез. Я попробую TDynArray при рефакторинге в будущем.
=====================================================================
Скажем, у меня есть класс TAtom
, как определено в следующем коде. Есть примерно от hundreds
до генерации кода TAtom во время выполнения, на данный момент thousands
. Во время выполнения мне нужно выполнить простую математику с плавающей запятой на stored in a dynamic array
всех существующих экземпляров TAtom чаще, чем TAtom.X/Y/Z
раз в секунду.
Теперь мне нужно добавить возможность создания кодов кодовых кодов, кодовых кодов и кодовых кодов экземпляров TAtom во время выполнения. Похоже, что я могу выбрать (1) запросить большой массив; (2) придерживаться динамического массива и вручную SetLength; (3) перейти на обычный TList; (4) переключиться на обычный TObjectList.
Я хочу избежать (1), если в этом нет необходимости, потому что тогда мне придется изменить довольно много сигнатур функций. (2) тоже выглядит не очень хорошо, потому что TList / TObjectList, похоже, создан для этой задачи. Однако, поскольку требуется приведение типов с использованием обычного TList / TObjectList, может ли кто-нибудь прокомментировать возможное снижение производительности? Я имею в виду, что было бы лучше, если бы можно было оценить нагрузку на производительность, прежде чем я переписываю код. Если производительность заметно упадет, могу ли я использовать другие методы?
Кроме того, мне интересно, есть ли разница в производительности между использованием TList и TObjectList?
родовое словоРешение
Если вы используете генерирующий код тегов и нет необходимости в преобразовании.
Производительность должна быть удовлетворительной для описываемого вами использования.Вставка сложнее, чем добавление в конец, потому что вам нужно сдвинуть элементы после точки вставки вверх по списку.
До тех пор, пока вы избегаете Generics.Collections.TObjectList<TAtom>
и выбираете более разумную стратегию распределения, динамические массивы эквивалентны всем классам SetLength(A, Length(A)+1)
.
Иногда у меня возникали проблемы с производительностью и фрагментацией памяти при попытке сохранить большие списки в виде непрерывных блоков памяти.Затем я прибег к схеме суб-распределения.Но поскольку ваши списки содержат ссылки на объекты, которые по сути являются указателями, у вас уже есть неявное суб-распределение.
Все это несколько умозрительно, и вам действительно нужно измерить - иначе мы можем только догадываться.
Другие советы
Могу я добавить еще один вариант в ваш список?
Если вы не используете какую-либо функцию наследования для данных в TAtom
, вы можете использовать record
вместо class
. Каждый экземпляр класса должен быть выделен в памяти, заполнен нулями и инициализирован индивидуально. Getmem/Freemem
всегда требует затрат, и фрагментация памяти возрастает.
Предварительно назначенный динамический код генерирующего тега будет быстрее, чем добавление отдельных экземпляров класса. И данные лучше подходят для кэша L1 / L2 ЦП.
Для вставки и удаления массив таких записей будет медленнее, чем array of record
, если у вас огромное количество элементов, потому что будет больше данных для удаления / вставки (оба TList
поддерживают только список указателей). Для еще более быстрой вставки / удаления лучше использовать связанный список.
Из-за внутреннего уведомления в механизме TList/TObjectList
возникают некоторые накладные расходы. механизм. И свойство TList/TObjectList
может быть немного медленнее (из-за проверки диапазона), чем использование непосредственно динамического массива.
Но с нашей оболочкой TDynArray вы можете придерживаться динамического массива, и по-прежнему иметь хорошую производительность, функции предварительного выделения и методы, похожие на генерирующий код. И даже больше доступных методов, таких как GetItem()
, сортировка с внешними индексами и тому подобное ...
Работает с Delphi 6 до XE.
С более новой версией Delphi, поддерживающей дженерики, вам лучше пойти в этом направлении.
Однако, поскольку приведение типов необходимо использовать обычный TList / TObjectList, может кто-нибудь прокомментируйте возможную производительность ударил?
Если вы вводите приведение с помощью формы
родовое слово, поскольку будут добавлены небольшие накладные расходы, которые действительно могут сложиться в вашей ситуации.Однако, если вы жестко приведете тип
родовое словоНасколько мне известно, накладных расходов фактически не возникает, поскольку приведение типов выполняется без проверок, и я также считаю, что это выполняется во время компиляции.
Что касается другого аспекта вашего вопроса, я думаю, что все они уже рассмотрены должным образом ...
Поскольку TObjectList является прямым потомком TList, производительность будет очень близка.
Первый вопрос: мы говорим о 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 контейнерах предварительно выделяет память в больших частях. При правильном коде между этими альтернативами не должно быть значительной разницы!
Создайте тестовый проект и измерьте время добавления, вставки и удаления тысяч экземпляров TAtom, используя четыре метода.Затем решите, какой из них использовать.
TList и TObjectList, вероятно, быстрее, чем динамический массив при добавлении, вставке и удалении, потому что динамический массив постоянно должен перераспределяться.Реализация TList и TObjectList этого не делает.
TList
и т. д. делают именно то, что должен делать код, работающий с фрагментами памяти или динамическими массивами, но их реализация уже оптимизирована для общего случая и включает соображения о том, как ведет себя диспетчер памяти.
Одним из критериев может быть соотношение операций чтения / обновления к последовательности.Если после создания последовательность обновляется нечасто, то должна быть заметна лучшая скорость с динамическими массивами, потому что для доступа к элементам с TList
и т.п. требуется один вызов метода плюс проверка границ, плюс проверка типа, если вы используете оператор as
.
В конце концов, затраты на арифметические операции, выполняемые с помощью TAtom
, должны преобладать во время выполнения, поэтому выбор dynarray или TListXXX
не имеет значения.
Динамические массивы записей будут иметь наименьшее влияние при доступе к элементам, если ваши атомы являются объектами, тогда все списки будут в некоторой степени эквивалентными с точки зрения скорости доступа.
Однако, если вы выполняете много из них, вашей ключевой проблемой будут вставки и удаления, для которых все списки и массивы будут работать плохо, но это то, что вам скажет профилирование. В этом случае вы можете подумать:
- связанный список, если вам не нужен доступ к атомам по индексу
- дерево, если вы используете его для пространственного раздела ваших атомов, вы также можете использовать этот раздел для хранения ваших атомов, а не для массива.
- разрешение элементов undefined / nil в вашем массиве / списке и поддержание стека элементов undefined / nil и индекса, если вам нужен отсортированный список (потенциально наиболее производительное решение, но также, вероятно, наиболее сложное для правильного использованияс точки зрения эффективности)