(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Написание оптимального кода под Delphi

Источник: delphimaster

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

  Компилятор Delphi относится к разряду оптимизирующих. Но насколько качественно проводится оптимизация? Как "помочь" компилятору создать более быстрый код? Давайте разберемся с этим на экспериментах.

Оптимизация константных выражений

Пример 1:

const
z = 15616;
...
var
a,b: integer;
...
a := $abcd6123;
b := z+a;
....

  С точки зрения оптимизации код можно упростить еще на этапе компиляции до

b:=15616+$abcd6123;

или того проще:

b:=$ABCD9E23;

  Но написанный выше листинг преобразуется в

mov eax, $abcd6123
lea ebx, [eax + $00003D00]

  С одной стороны компилятор не "сообразил", что значение переменной "a" можно преобразовать в константу и сложить с другой константой (которая, заметим, подставлена именно как константа) на этапе компиляции, с другой стороны был применен весьма хитрый трюк с LEA (об этом ниже). Тем не менее, код

mov ebx, $ABCD9E23 

в любом случае быстрее и короче.

Пример 2:

...
b:=random(maxint);  // b - заведомо не константа ! 
a:=$abcd6123;
if b>a then b:=a;
...

  Скомпилированный код будет выглядеть

mov eax, $7fffffff  // MaxInt 
call @RandInt
mov ebx,eax
mov eax, $abcd6123
cmp eax, ebx
jnl +$02
...

  А ведь значение, присвоенной переменной "а" являлось константой и наш пример можно было бы переписать как:

b:=random(maxint);
a:=$abcd6123;
if b>$abcd6123 then b:= $abcd6123;
Пример 3:

...
a:=$abcd6123;
b:=$abc34233;
c:=b-a;
...

  После компиляции получаем:

mov eax, $abcd6123
mov ebx, $abc34233
mov ebx, edx
sub ebx, eax

  Т.е. компилятор преобразовал код так, как он был написан, а ведь можно было бы просто записать:

mov ebx, $fff5e110
т.е. c:= $fff5e110;

Оптимизация алгебраических выражений

Пример 4:

...
var
a,b,c,d: integer;
p: pointer;
begin
p:=nil;
a:=0;
...
 {далее по коду эти присвоения не используются} 

  После компиляции эти переменные будут удалены, причем с предупреждением

Value assigned to ... never used
Пример 5:

...
a:=0;
b:=a;
showmessage(inttostr(b));
...

  Код скомпилируется как есть! Таким образом мы обманули компилятор псевдо использованием переменных. Delphi не исправляет нашей "кривости", поэтому эта задача ложится исключительно на плечи программиста.

Пример 6:

...
b:=random(maxint);
a:=b;
func(a,b);
a:=a+1;
func(a,b);
...

  Данный код можно оптимизировать до

...
b:=random(maxint);
func(b,b);
a:=b+1;
func(a,b);
...

  И этого Delphi за нас не сделает.

Пример 7:

...
c:=a div b;
c:=a*b;
...

  В данном примере первую строчку можно безболезненно удалить, что Delphi делать умеет.

Пример 8:

if ((a*b)<$3d00) and (a*b)>0)) then ...

  В данном случае можно избавится от одной операции умножения, присвоив значение выражения a*b временной переменной. Анализ ассемблерного листинга показывает, что компилятор именно так и поступает. Тем не менее, поменяв второе подвыражение на ((b*a)>0), компилятор принимает выражения за разные и генерирует умножение для обоих случаев, не смотря на то, что результат одинаков.

Оптимизация арифметических операций

Сложение и вычитание

  Применение инструкции LEA вместо ADD позволяет производить сумму 3х операндов (двух переменных и одной константы) за один такт. Трюк заключается в том представление ближних указателей эквивалентно их фактическому значению, поэтому результат, возвращенный LEA равен сумме ее операндов. При возможности Delphi производит такую замену.

Деление

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

mov esi,edi
sar esi,1
jns +$03
adc esi, $00

  Здесь учитывается особенность самой операции div - округление в большую сторону. Поэтому, если можно пренебрегать округлением, используйте c:=a shr 1 вместо с:=a div 2.

Умножение

  Умножение на степень двойки можно заменять сдвигами битов. Delphi заменяет умножение сдвигами при умножении на 4,8,16 итд. При умножении на 2 производится суммированием переменной с собой.

  Умножать на 3,5,6,7,8,10 и т. д. можно и без операции умножения - расписав выражение по формуле (a shl n)+a, где n - показатель степени двойки. Например, при умножении на 3 n=1. Delphi при возможности прибегает к этому трюку. Заметим, операнд LEA умеет умножать регистр на 2,4,8, что также при возможности используется компилятором. Например, умножение на 3 преобразуется в инструкцию

lea esi, [ebx + ebx*2]
Оптимизация case of

  Анализ скомпилированного кода показывает, что Delphi проводит утрамбовку дерева. Т.е. значения case сортируются и выбор нужного элемента производится при помощи двоичного поиска.

  В случае, если элементы case of выстраиваются в арифметической прогрессии, компилятор формирует таблицу переходов. Т.е. создается массив указателей с индексами элементов, поэтому выбор нужно элемента выполняется за одну итерацию независимо от количества элементов.

Оптимизация циклов

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

  Слияние циклов - не производится. Если два цикла, следующие друг за другом имеют одинаковые границы итерационной переменной, разумно оба цикла объединить в один.

  Вынесение инвариантного кода за пределы цикла - не выносится. Наиболее распространенный недочет - условие цикла записывается как:

for i:=0 to memo1.lines.count - 1 do...

  Delphi будет при каждой итерации вызывать метод count, вычитать из результата 1 и потом уже сверять. Настоятельно рекомендуется переписывать подобный код как

lin := .lines.count - 1;
for i:=0 to lin do...

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

  Замена циклов с предусловием на циклы с постусловием - производится. Циклы с постусловием имеют главное преимущество над другими видами циклов (с предусловием и с условием в середине) - они содержат всего одно ветвление. Delphi производит такую замену.

  Замена инкремента на декремент - не производится. Более того, даже декрементный цикл компилируется в неоптимальный код, т.к. не используется флаг ZF. Вместо этого происходит сравнивание значения регистра с 0.

  Удаление ветвлений - не производится.

Вывод:

  1. Не используйте переменные для временного хранения констант или обязательно объявляйте "магические" числа как const, либо подставляйте в код непосредственные значения
  2. Неиспользуемыми объявлениями и присвоениями можно безболезненно пренебрегать - Delphi умеет их вычищать.
  3. Внимательно следите за использованием переменных, в частности лишним присвоениям их значений друг другу. Такого рода оптимизации Delphi делать не умеет.
  4. Используйте свернутые математические выражения. (например, (3*a - a) /2 упрощается до a). Delphi не умеет упрощать математические выражения. (Да и что говорить, даже MathCAD не всегда грамотно умеет делать такие преобразования).
  5. Не используйте конструкции типа a:=10*sin(45*pi/180); Delphi не вычислит эту константу на этапе компиляции, напротив, будет послушно вызывать sin и pi по ходу выполнения программы! В случае, если угол является переменной, по крайней мере pi можно заменить константой 3,1415...
  6. Delphi прекрасно справляется с выражениями, полностью составленных из констант - они вычисляются на этапе компиляции.
  7. Внимательно следите за условиями и их границами. Компилятор Delphi не умеет обнаруживать заведомо ложных условий. Также он не умеет удалять заведомо лишние условия. Например, (a>0) and (a<15616) and (a<>0)
  8. Если в условии несколько раз проверяется одно и тоже выражение, следите, чтобы оно было выражено во всех конструкциях одинаково. В противном случае скомпилированный код будет не оптимален. Например, if ((a*b)>0) and ((a*b)<1024) then... При перестановке во втором случае b*a смысл выражения не изменится, но код будет иметь уже на одну операцию умножения, а две. Можно временно присвоить проверяемое выражение временной переменной, а затем уже проверять полученное значение.
  9. Сообщение "Combining signed and unsigned types - widened both operands" сообщает не только о потенциальной ошибке - также вследствие преобразования мы теряем производительность. Например, z - объявлена как ineteger. условие if z>$abcd6123 then z:= $abcd6123; несмотря на его правильность вызовет данное предупреждение. Сгенерированный код будет, выполнять преобразования величин до 64-х бит, и дальнейшее уже сравнение 64-х битных операндов. Если изменить тип z на cardinal, мы избавимся от предупреждения и получим 3 строки кода, вместо 8 !
  10. Delphi умеет оптимизировать сложение, умножение и частично деление. При делении на степень двойки, если не важно округление до большего, рекомендуется пользоваться shr 1 вместо div 2.
  11. В case of при возможности используйте элементы, расположенные в арифметической прогрессии. Тем не менее, даже при невыполнении данного условия мы получим качественный код после утрамбовки дерева.
  12. Выносите инвариантный код за тело цикла. Наиболее частая ошибка - for i:=1 to length(str) do... Дело в том, что при каждой итерации будет вызываться функция length, что пагубно скажется на производительности. Рекомендуется длину строки заранее присвоить переменной. Также не включайте в тело цикла код, заведомо не зависящий от изменения итерационной переменной.

  Сравнивая Delphi с компиляторами Visual C++, WATCOM, Borland C++ (тестирование данных компиляторов приведено в [1]) приходим к выводу, что Delphi по своим оптимизирующим свойствам аналогичен Borland C++ (а кто сомневался? ;) ). Учитывая, что Borland C++ по итогам сравнения оказался последним, делаем несложный вывод. Весьма печален и тот факт, что большинство кода VCL написано с точки зрения "красоты" кода, а не его оптимальности с точки зрения скорости. Например, не соблюдается правило 12.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Обсудить материал в конференции Embarcadero » [1]
Написать редактору 
 Рекомендовать » Дата публикации: 05.05.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
Купить CommView for WiFi 1 лицензия
Panda Global Protection - ESD версия - на 1 устройство - (лицензия на 1 год)
IBM RATIONAL Clearcase Floating User License + Sw Subscription & Support 12 Months
ABBYY Lingvo x6 Многоязычная Домашняя версия, электронный ключ
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Компьютерный дизайн - Все графические редакторы
СУБД Oracle "с нуля"
Новые программы для Windows
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
Обсуждения в форумах
Разработка устройств на микроконтроллерах (37)
Профессиональный программист. Основная специализация: МИКРОКОНТРОЛЛЕРЫ, АССЕМБЛЕР для любых...
 
Написание программ для микроконтроллеров AVR, PIC, ARM, STM32 (23)
Напишу любую программу на любом искусственном языке. Профессиональный программист. Основная...
 
Пишу программы на заказ профессионально (3233)
Пишу программы на заказ на языках Pascal (численные методы, списки, деревья, прерывания) под...
 
Сергей Ковалевский: Хватит платить Западу - у России должна быть своя ОС (68)
> На сегодняшний день открытых СУБД не существует. Есть довольно > примитивный MySQL, а все...
 
Разработка программ базы данных (44)
Написание прикладных компьютерных программ (базы данных) на заказ. Разработка корпоративных...
 
 
 



    
rambler's top100 Rambler's Top100