Архивы: по дате | по разделам | по авторам

Как сделать свою программу быстрой

Архив
автор : Андрей Гапанович   11.07.2003

Нужна ли оптимизация кода? Конечно, приятно сознавать, что твой код совершенен, но если никто, кроме тебя самого, этого не оценит, стоит ли тратить время? Тем более что программист, как правило, работает в жесточайшем цейтноте.

В те далекие времена, когда компьютеры были не быстрее черепахи, многие программисты писали свои творения, буквально вылизывая каждую строку кода, чтобы хоть немного повысить скорость работы программ. С развитием аппаратной части быстродействие компьютеров стремительно росло и за последнее десятилетие увеличилось на порядки. Обычный персональный компьютер сейчас в разы превосходит вычислительной  большие ЭВМ советских времен, занимавшие целые комнаты. Такое положение дел разбаловало программистов, особенно начинающих, — ведь типичные приложения зачастую используют менее десяти процентов ресурсов процессора. Может, оптимизация кода и вовсе не нужна? Конечно, приятно сознавать, что твой код совершенен, но если никто, кроме тебя самого, этого не оценит, стоит ли тратить время? Тем более что программист, как правило, работает в жесточайшем цейтноте. Нужна ли оптимизация ради оптимизации, и нельзя ли во многих случаях обойтись без нее?

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

В некоторых случаях оптимизация необходима даже без всяких тестирований. К ним можно отнести написание библиотек и драйверов, программ, выполняющих сложные математические преобразования, например mpeg-кодирование, распознавание текста. Необходимость оптимизации библиотек обусловлена тем, что, помимо кода самой библиотеки, нагрузку на процессор будет создавать и программа, использующая эту библиотеку. В этом смысле драйверы немного похожи на библиотеки. Их функции тоже используются другим кодом. Но стоит заметить, что драйверы, как правило, имеют высокий приоритет в операционной системе, и если код драйвера будет работать медленно, это сильно скажется на быстродействии всей системы. Существует много программ, которые занимаются сложными математическими преобразованиями. Даже при нынешних вычислительных возможностях их работа занимает много времени, и ускорение даже на несколько десятков процентов дает ощутимую экономию времени, особенно если эти программы используются часто.

Итак, мы решили, что программа работает недопустимо медленно и надо постараться ускорить ее работу. Как этого достичь? Наибольший эффект, как правило, дает замена медленных алгоритмов более быстрыми. Возьмем классический пример — поиск. Предположим, у нас есть список строк, и мы ищем среди них нужную. Когда строк не много, проще всего последовательно перебирать их, пока не найдется нужная. Когда строк около тысячи, потребуется в среднем около пятисот сравнений. Но если список предварительно отсортировать и применить бинарный поиск, то можно обойтись в среднем десятком сравнений. То есть получаем увеличение скорости работы в пятьдесят раз. И чем больше строк в списке, тем больше выигрыш в скорости.

Если вы используете в своей программе сторонние библиотеки (а так бывает практически всегда, поскольку на сегодняшний день нереально писать программы, не используя хотя бы функции системных библиотек), может оказаться, что в медлительности программы виновата именно чужая библиотека. Ее ведь тоже писали программисты, которые могли отложить оптимизацию на более поздний срок или вовсе про нее забыть. Тогда стоит поискать другие библиотеки, выполняющие те же функции, или даже попробовать реализовать их самим. Самостоятельная реализация нужных функций может дать эффект, если вы используете лишь небольшую часть сложной библиотеки. В таких библиотеках все функции часто взаимосвязаны, и при вызове одной из них может вызываться большое количество других вспомогательных функций, которые вам совершенно не нужны. Именно за счет этого можно увеличить быстродействие. Если вы используете всю мощь библиотеки, не пытайтесь писать ее самостоятельно. Во-первых, это отнимет очень много времени, во-вторых, при ее реализации можно столкнуться с множеством проблем, которые давным-давно решены. Лучше обратить внимание на аналогичные библиотеки других разработчиков.

Повысить быстродействие можно и учитывая особенности процессора. Например, используя дополнительные инструкции из наборов MMX, 3DNow, 3DNowEx, SSE, SSE2, можно значительно ускорить обработку мультимедийных данных, распознавание текста и т. п. Так, обнулить регистр можно следующими способами:

- sub ax,ax;
- mov ax,0;
- xor ax,ax.

Третий вариант — самый быстрый, а два других гораздо медленнее. Конечно, если эта инструкция выполняется единожды, разницы никто не заметит. А если это происходит в цикле, который вызывается миллионы раз, то она вполне ощутима, особенно если подобные изменения сделать не в одном месте. К счастью, многие компиляторы сейчас имеют возможность оптимизации, и такие процессорные нюансы учитываются ими автоматически. Хотя думать «по-человечески» никакие автоматические оптимизаторы не умеют, и не помешает глянуть, какой код они генерируют. В некоторых случаях вообще целесообразно отдельные участки писать на ассемблере, так как код получается компактнее и быстрее, чем сгенерированный компилятором.

Теперь самый важный вопрос — в каких местах оптимизировать код. Если оптимизировать всю программу, на это уйдет очень много времени и большая часть труда будет потрачена впустую. Оптимизировать нужно именно те участки, которые более всего тормозят общую производительность. Для их определения используются профайлеры. Профайлер — это программа или модуль, который засекает время и частоту выполнения отдельных участков программы и дает возможность программисту проанализировать эти сведения. Профайлеры существуют для многих языков и зачастую входят в состав самих систем программирования. Работу с профайлером я рассмотрю на примере Delphi, как наиболее распространенной на просторах бывшего Советского Союза системы программирования. Встроенный профайлер в Delphi отсутствует, зато существуют профайлеры сторонних разработчиков. Вот некоторые из них:

- ProDelphi (www.prodelphi.de),
- Delphi Profiler (www.axiomati.demon.co.uk),
- GpProfile (www.eccentrica. org/gabr/gpprofile/gpprofile.htm).
На мой взгляд, GpProfile является наилучшим — это open source-проект, и при желании можно скачать исходные тексты и доработать под свои нужды.

GpProfile представляет собой модуль gpprof.pas и исполняемый файл gpprof.exe. Прежде всего, необходимо открыть проект, «узкие» места которого мы будем искать. GpProfile просканирует все модули вашего проекта и отобразит все их функции, процедуры и методы. Далее от вас требуется отметить те процедуры и функции, которые вы собираетесь исследовать (рис. 1). Можно, конечно, выбрать все, но тогда будет гораздо сложнее разбираться в результатах тестирования. Если вы хотите улучшить производительность какого-то определенного действия пользователя, то отметьте именно те функции, которые связаны с этим действием. Следующим шагом следует выбрать пункт меню Project/Instrument. При этом GpProfile изменит модули вашего проекта, в которых встречаются отмеченные функции, создав при этом резервную копию исходного кода. Изменения сводятся к следующему:
- В секции uses добавляется строка {>>GpProfile U} GpProf, {GpProfile U>>}, подключая модуль, который упоминался ранее.

- В начале каждой процедуры, функции или метода добавляется {>>GpProfile} ProfilerEnterProc(1); try {GpProfile>>}. С помощью вызова ProfilerEnterProc засекается начало выполнения процедуры. В качестве аргумента выступает номер, уникальный для каждой функции.

- В конце каждой функции добавляется {>>GpProfile} finally ProfilerExitProc(6); end; {GpProfile>>}. Здесь засекается время завершения работы функции.

После завершения работы программы накопленная информация обрабатывается и показывается в окне exe-модуля GpProfile (рис. 2). Статистику можно анализировать по модулям, классам, процедурам. Для каждой процедуры можно узнать следующие статистические данные:

- Time — общее время, в течение которого работала процедура;
- w/Child — общее время, в течение которого работала процедура, включая вызовы дочерних процедур;
- Calls — количество вызовов процедуры;
- Min/Call, Max/Call, Avg/Call — минимальное, максимальное и среднее время одного выполнения процедуры;
- % Time — процент времени работы процедуры от общего времени работы отмеченных процедур.

Теперь у нас есть информация о том, какие участки кода работают долго и какие вызываются чаще других. Оптимизации этих процедур стоит уделить наибольшее внимание. Следует отметить недостаток этого профайлера — нельзя размещать метки в произвольных местах, поэтому если вы хотите подробнее исследовать работу достаточно длинной процедуры, придется разбить ее на более мелкие. После изменения кода запускайте профайлер снова, смотрите, какая процедура теперь «съедает» больше всего времени, оптимизируйте ее и так до тех пор, пока не добьетесь удовлетворительной скорости. Для каждой программы существует определенный предел, преодолеть который не удастся, применяя даже самые изощренные способы оптимизации. Но все же с помощью описанной методики обычно удается существенно повысить скорость работы, а иногда и вовсе избавиться от медлительности программы. После завершения оптимизации уберите метки, которые вставил в код GpProfile. Для этого выберите пункт меню Project/Remove instrumentation, и ваш код примет исходный вид. Но изменения, произведенные вами для повышения быстродействия, конечно же, останутся.

Девять полезных советов

1 При написании кода программ старайтесь избегать процедур, состоящих из сотен строк. Практически всегда в них можно выделить блоки, которые лучше оформить в виде отдельной процедуры. Возможно, позже вы ею даже воспользуетесь где-то в другом месте. Не говоря уже о том, что это повышает понимание программы и вами, и другими программистами. К тому же так проще искать «узкие» места в вашей программе.
2 Пользуйтесь оператором case вместо многократных if… else if… Во втором варианте компилятор будет выполнять проверку условия столько раз, сколько у вас вариантов. В первом проверка выполняется лишь однажды.
3 Некоторые действия могут быть довольно продолжительными, поэтому выносите за рамки цикла все, что можно выполнить вне его, чтобы избежать большого числа повторений внутри цикла.
4 В циклах типа for нужно стараться, чтобы значение счетчика уменьшалось до нуля, а не наоборот — начиналось с нуля. Это связано с особенностями процессора. Сравнение с нулем выполняется гораздо быстрее, чем с другим числом.
5 Старайтесь пользоваться типом Variant только при необходимости. Операции над этим типом сложнее, чем, например, над Integer или String.
6 Не используйте компонент TTreeView для хранения древовидных структур данных — он работает очень медленно и предназначен только для отображения. Когда я заменил этот компонент самодельным деревом на основе TList, программа стала работать быстрее примерно в сто раз.
7 Не пытайтесь приспособить RichEdit для подсветки синтаксиса. Он для этого не предназначен и работает в данном случае очень медленно.
8 Сохранение и загрузка свойств компонентов с помощью методов ReadComponent и WriteComponent работает довольно медленно, поэтому по возможности старайтесь сохранять и восстанавливать состояние программы между сеансами при помощи других способов.
9 Если вы заранее знаете, что в списке для поиска будет много элементов, постарайтесь отсортировать его и применять бинарный поиск вместо линейного.

© ООО "Компьютерра-Онлайн", 1997-2022
При цитировании и использовании любых материалов ссылка на "Компьютерру" обязательна.