У меня есть программа на Delphi 2009, которая обрабатывает много данных и должна работать как можно быстрее и не использовать слишком много памяти.
Какие изменения маленький простой вы внесли в свой код Delphi, которые оказали наибольшее влияние на производительность вашей программы за счет заметного сокращения времени выполнения или использования памяти?
Спасибо всем за все ваши ответы. Множество отличных советов.
Для полноты картины я опубликую несколько важных статей по оптимизации Delphi, которые я нашел.
Прежде чем приступить к оптимизации кода Delphi на About.com
Скорость и размер: 10 главных приемов также на About.com
Основы оптимизации кода и Рекомендации по оптимизации Delphi в High Performance Delphi, относящиеся к Delphi 7, но все еще очень актуальные.
В общем, точнее.
Я ищу все, что может помочь. Первые 3 ответа превосходны и уже дали мне новые идеи. Будьте изобретательны и просто подумайте, что помогло вам больше всего.
Помните, Дональд Кнут сказал: «Преждевременная оптимизация - корень всех зол (или, по крайней мере, большей их части) в программировании».
Да, но: «и наоборот, при разработке программного обеспечения на системном уровне вопросы производительности всегда следует рассматривать с самого начала». См .: acm.org/ubiquity/views/v7i24_fallacy.html
Хорошо, я добавил несколько. Многое зависит от ваших конкретных обстоятельств.


Отделение логики программы от пользовательского интерфейса, рефакторинг, а затем независимая оптимизация наиболее часто используемых и наиболее ресурсоемких элементов.
Выключите отладку
Включите оптимизацию
Удалите все ссылки на единицы, которые ты на самом деле не используешь
Ищите утечки памяти
> Выключите отладку. Это не влияет на размер кода или скорость кода. Отладочная информация хранится в файлах dcu и dcp, но не добавляется в исполняемый файл.
Предварительное распределение списков и массивов, а не их увеличение с каждой итерацией.
Это, вероятно, оказало на меня наибольшее влияние с точки зрения скорости.
При работе с tstringlist (или подобным) устанавливайте «sorted: = false» до тех пор, пока не понадобится (если вообще). Кажется, нетрудно ...
Разумно используйте SetLength () для строк и массивов. Оптимизируйте инициализацию с помощью FillChar или ZeroMemory.
Локальные переменные, созданные в стеке (например, типы записей), работают быстрее, чем переменные, выделенные в куче (объекты и New ()).
Используйте объекты повторно, а не уничтожайте, а затем создавайте. Но убедитесь, что код управления для этого работает быстрее, чем менеджер памяти!
FastMM работает быстро, поэтому код управления для повторного использования объектов должен быть очень быстрым. Это определенно то, что вы хотите профилировать, но это может иметь огромное значение, если все сделано правильно.
Дополнительные сведения о кэшировании объектов и их воссоздании: stackoverflow.com/questions/257428
Следовательно, «Но убедитесь, что код управления для этого быстрее, чем диспетчер памяти!» - Хорошо, если это что-то, что можно очистить с помощью FillChar или с помощью простого присваивания (используйте метод «Initialise (params)», а не «Create (Params)». В противном случае, вероятно, не стоит.
Проверьте часто используемые циклы на предмет вычислений, которые могут быть (хотя бы частично) предварительно рассчитаны или обработаны с помощью таблицы поиска. Для этого используются триггерные функции, но они применимы и ко многим другим.
Если вам нужно использовать Application.processmesssages (или аналогичный) в цикле, попробуйте вызывать его только каждую N-ю итерацию.
Точно так же, обновляя индикатор выполнения, не обновляйте его на каждой итерации. Вместо этого увеличивайте его на x единиц каждые x итераций или масштабируйте обновления по времени или в процентах от общей длины задачи.
.BeginUpdate;
.EndUpdate;
;)
В основном при обновлении элементов управления графическим интерфейсом. ListView.Items.BeginUpdate; ... ListView.Items.EndUpdate;
+1 - удивительно, какая разница, когда вы добавляете много вещей в списки, заметки и т. д.
Также может помочь при построении SQL с использованием qry.SQL.Add. Посмотрите, что происходит из TStringList.OnChange, если для запроса ADO назначено соединение - могут быть двусторонние обходы на уровень OLEDB
BeginUpdate и endupdate больше не помогут, если у вас есть тысячи элементов. Ваше приложение будет зависать на секунды или минуты, если вы не используете TListview или TListbox в виртуальном режиме. Вот тогда вы действительно заметите прирост производительности.
Сможет ли кто-нибудь отредактировать этот ответ, чтобы немного лучше объяснить, для чего используются .BeginUpdate и .EndUpdate или когда их следует использовать? Я не знаком с этой функцией (пока), но она кажется полезной.
Используйте множество утверждений для отладки, а затем отключите их в коде доставки.
Отключите проверку диапазона и переполнения после тщательного тестирования.
Если вам действительно, действительно, действительно нужно быть легким, вы можете отказаться от VCL. Взгляните на KOL & MCK. Конечно, если вы это сделаете, то вы обмениваете функции на уменьшение занимаемой площади.
Используйте полную версию FastMM и изучите документацию и исходный код и посмотрите, сможете ли вы настроить его в соответствии со своими требованиями.
Воспользуйтесь преимуществом кода проект FastCode. Части этого были включены в собственно VCL / RTL (например, FastMM), но есть еще кое-что, что вы можете использовать!
Обратите внимание, у них есть новый сайт, они тоже перемещаются, но, похоже, он немного неактивен.
Может воспользоваться Пакет исправлений VCL от Андреас Хаусладен
The VCL Fix Pack is a Delphi unit that fixes VCL and RTL bugs at runtime by patching the original functions. If you want all IDE Fix Pack fixes in your application this unit is what you need. Adding the unit to your project (Delphi and C++Builder) automatically installs the patches that are avilable for your Delphi/C++Builder version.
Пакет исправлений VCL исправляет только ошибки RTL и VCL. Это не дает вам большей скорости.
Все, что убивает жалкие ошибки, ускоряет работу, даже если только повышает производительность. Спасибо за ваши программы и модули, такие как VLC Fix Pack, Andreas ;-)
Используйте инструмент профилирования Delphi (Some здесь или здесь) и обнаруживайте свои собственные узкие места. Оптимизация неправильных узких мест - пустая трата времени. Другими словами, если вы примените все эти предложения здесь, но проигнорируете тот факт, что кто-то поместил сон (1000) (или аналогичный) в какой-то очень важный код, это пустая трата вашего времени. Сначала устраните ваши настоящие узкие места.
Да, это лучшее решение на данный момент. И лучший профилировщик, который я видел, - это AQTime. Они предлагают старую версию для бесплатной загрузки. Это не последняя версия, но все еще работает очень хорошо. download.com/AQtime/3000-18487_4-50535.html
AQtime может быть не лучшим для всех. См .: stackoverflow.com/questions/368938/delphi-profiling-tools - но спасибо за ссылку на бесплатную версию. Мне нужно посмотреть, работает ли он с Delphi 2009.
Оно делает. Я это уже тестировал.
@ Мейсон: Это не бесплатная загрузка. Старая загрузка теперь перенаправляет на пробную версию для 6.11.
+1. Менять рабочий код ради оптимизации без профилирования - безумие.
Вы можете рассмотреть возможность использования пакетов времени выполнения. Это может уменьшить объем используемой памяти, если выполняется более одной программы, написанной с использованием одних и тех же пакетов.
Рассмотрите проблемы с оборудованием. Если вам действительно нужна производительность, подумайте, на каком жестком диске (-ах) работает ваша программа и ваши базы данных. Есть много переменных, особенно если вы работаете с базой данных. RAID - тоже не всегда лучший ответ.
Подумайте об осторожном использовании ниток. Если вы сейчас не используете потоки, подумайте о добавлении пары. Если да, убедитесь, что вы не используете слишком много. Если вы работаете на двух- или четырехъядерном компьютере (которых больше нет), очень важна правильная настройка потоков.
Вы можете посмотреть Библиотека OmniThread от Габра, но есть ряд библиотек потоков, разрабатываемых для Delphi. Вы можете легко реализовать свою собственную параллель для использования анонимных типов.
AsyncCalls [andy.jgknet.de/blog/?page_id=100] также заслуживает внимания с чисто функциональной точки зрения.
Для старой разработки BDE, когда я только начал Delphi, я использовал множество компонентов TQuery. Кто-то сказал мне использовать мастер-деталь TTable после того, как я объяснил ему, что я делаю, и это заставило программу работать намного быстрее.
Вызов DisableControls может пропустить ненужные обновления пользовательского интерфейса.
Касательно 3. Ничего не меняйте, не измерив должным образом эффект, который это имеет. Использование структур данных с интенсивным использованием памяти может фактически замедлить работу, если шаблоны доступа к памяти постоянно делают недействительными строки кэша.
Прежде чем что-либо делать, определите медленные части. Не трогайте рабочий код, который работает достаточно быстро.
Уменьшите количество операций с диском. Если памяти достаточно, загрузите файл полностью в ОЗУ и выполняйте все операции в памяти.
+1 по этому поводу. Кроме того, даже если у вас есть весь файл в памяти, обрабатывайте его таким образом (последовательно), чтобы содержимое кеша сохранялось в максимально возможной степени. Применимо к размеру данных >> размеру кэша процессора, естественно.
Прекратите использовать TStringList для всего.
TStringList - это нет, структура данных общего назначения для эффективного хранения и обработки всего, от простых до сложных типов. Ищите альтернативы. Я использую Библиотека контейнеров и алгоритмов Delphi (DeCAL, ранее известный как SDL). Julians EZDSL также должен быть хорошей альтернативой.
Если вы используете потоки, установите их привязку к процессору. Если вы еще не используете потоки, подумайте об их использовании или изучите асинхронный ввод-вывод (порты завершения), если ваше приложение выполняет много операций ввода-вывода.
Подумайте, действительно ли база данных СУБД - идеальный выбор. Если вы только читаете данные и никогда не меняете их, то простой файл с фиксированной записью может работать быстрее, особенно если путь к данным может быть легко отображен (например, один индекс). Тривиальный двоичный поиск по фиксированному файлу записи по-прежнему очень быстр.
При идентификации записей по возможности используйте целые числа для сравнения записей. Хотя первичный ключ «название компании» может показаться логичным, время, потраченное на создание и сохранение хэша, значительно сократит общее время поиска.
Но печальный ответ заключается в том, что настройка и оптимизация дадут вам, возможно, 10% улучшение (и это опасно); редизайн может дать вам 90%. Как только вы действительно поймете цель, вы часто сможете переформулировать проблему (и, следовательно, решение) в гораздо лучших терминах.
Ваше здоровье
Извините, но это ужасное «практическое правило». Реальная правда в том, что вы не можете знать, сколько вы получите от настройки и оптимизации, пока не профилируете. Затем вы можете увидеть, подходит ли настройка или переделка. А если это опасно, значит, вы делаете это неправильно. Тест, рефакторинг, тест.
Осмотрите все шлейфы и найдите способы короткого замыкания. Если вы ищете что-то конкретное и находите это в цикле, используйте команду BREAK, чтобы немедленно выйти из строя ... нет смысла повторять цикл через все остальное. Если вы знаете, что у вас нет совпадения, используйте ПРОДОЛЖИТЬ как можно быстрее.
если вам нужно выполнить сравнение строк, используйте оптимизированные функции STRCOMP или TEXTCOMP. Для простого равенства используйте оптимизированные функции SAMESTR и SAMETEXT. Всегда выбирайте SAMESTR / STRCOMP, если вы знаете, что случай всегда будет одинаковым.
Самое большое улучшение произошло, когда я начал использовать AsyncCalls для преобразования однопоточных приложений, которые раньше замораживали пользовательский интерфейс, в (своего рода) многопоточные приложения.
Хотя AsyncCalls может намного больше, я нашел его полезным для этой очень простой цели. Допустим, у вас заблокирована подпрограмма следующим образом: кнопка «Отключить», «Выполнить работу», «Включить». Вы перемещаете часть Do Work в локальную функцию (назовите ее AsyncDoWork) и добавляете четыре строки кода:
var a: IAsyncCall;
a := LocalAsyncCall(@AsyncDoWork);
while (NOT a.Finished) do
application.ProcessMessages;
a.Sync;
Для вас это запускает AsyncDoWork в отдельном потоке, в то время как ваш основной поток остается доступным для ответа на пользовательский интерфейс (например, перетаскивание окна или нажатие кнопки «Прервать»). Когда AsyncDoWork завершается, код продолжается. Поскольку я переместил его в локальную функцию, все локальные вары доступны, и код менять не нужно.
Это очень ограниченный тип «многопоточности». В частности, это двухпотоковая передача. Вы должны убедиться, что ваша функция Async и пользовательский интерфейс не имеют доступа к одним и тем же компонентам VCL или структурам данных. (Я отключаю все элементы управления, кроме кнопки остановки.)
Я не использую это для написания новых программ. Это просто действительно быстрый и простой способ сделать старые программы более отзывчивыми.
Хотя на этот вопрос на самом деле нет ответа, я даю вам «принятый ответ», потому что он больше, чем любой другой, выглядит как очень простое решение многих проблем, и тем не менее я не слышал о нем раньше. . Я знаю о тредах, и мне не терпелось попасть в них. Но если AsyncCalls настолько просты (да, я знаю, как держать их подальше от других данных), то программисты Delphi должны помнить об этом. Мги упоминал об этом раньше, но ваше описание превосходное.
О боже, опрос и вводит ошибку повторного входа.
Нет необходимости в опросе. Эта строка делает то же самое лучше: MsgAsyncMultiSync ([AsyncInterface], False, SleepTime, QS_ALLINPUT или QS_ALLPOSTMESSAGE);
Если у вас есть список, используйте динамический массив чего угодно, даже следующую запись:
Для этого не нужны классы, нет освобождения, и доступ к нему очень быстрый. Даже если ему нужно вырасти, вы можете это сделать - см. Ниже. Используйте TList или TStringList, только если вам нужна большая гибкость в изменении размера.
type
TMyRec = record
SomeString : string;
SomeValue : double;
end;
var
Data : array of TMyRec;
I : integer;
..begin
SetLength( Data, 100 ); // defines the length and CLEARS ALL DATA
Data[32].SomeString := 'Hello';
ShowMessage( Data[32] );
// Grow the list by 1 item.
I := Length( Data );
SetLength( Data, I+1 );
..end;
Избегайте использования TTable с полями подстановки, когда подойдет TQuery.
У меня был отчет, который работал очень медленно в большой базе данных. Он использовал TTable с кучей полей поиска. Я повесил сетевой монитор на свое приложение и обнаружил, что огромное количество данных текло по строкам, когда я проходил через этот TTable с полями поиска. Переход на TQuery резко сократил объем трафика и существенно изменил скорость.
Этот совет на самом деле просто учит мыслить в терминах клиент-сервер.
Запустите SysInternals ProcessExplorer и FileMonitor и наблюдайте за поведением вашего приложения с точки зрения ОС. Вы найдете сюрпризы, такие как неожиданная активность диска и реестра. Если вы могли подумать, что сохраняете свои настройки в реестр или в файл .ini за одну операцию, возможно, вы выполняете 100 операций записи. Вы можете обнаружить, что для записи в базу данных требуется 30 операций записи, когда вы думали, что делаете 3. Некоторые из них можно настроить с помощью таких вещей, как транзакции и буферизация. Но не раньше, чем вы сначала найдете проблемные места. У меня было такое «пробуждение», когда я прошел сертификацию U3 для своего приложения (у накопителей SanDisk U3 есть собственная сертификация). Я никогда не зарабатывал много денег, имея версию своего приложения для U3, но упражнение того стоило.
По возможности избегайте thread.synchronize. Это останавливает все и ждет поток VCL. Мы изменили большую часть наших синхронизаций, чтобы использовать thread.queue, где они могли бы выполняться асинхронно. Здесь также помогает использование анонимных методов.
Вы должны указать конкретную проблему, чтобы мы могли попытаться ответить.