Оптимизиране на асемблерните езикови програми

Рей Дънкан.

В предишната част обсъдихме някои общи проблеми с оптимизацията и след това говорихме за компромисите, които трябва да се направят при оптимизиране на производителността и размера на програмата. В това и следващото ще разгледаме по-подробно някои от класическите примери за „локална“ оптимизация. Но е важно да запомните, че тези конкретни техники трябва да се използват само при определени обстоятелства - а именно, след като сте се уверили, че сте приложили правилните алгоритми и структури от данни, че сте дебъгвали програмата изцяло и инструментите за профилиране имат ви показа самите фрагменти от програмата, които ограничават производителността.

Отхвърляне на универсалността

Операциите на умножение и деление изискват много усилия от почти всеки процесор, тъй като те трябва да бъдат внедрени (в хардуер или софтуер) чрез съответно смени и добавяния или отмествания и изваждания. Старите 4- и 8-битови процесори не съдържаха машинни инструкции за умножение или деление, така че тези операции трябваше да бъдат изпълнени с помощта на дълги подпрограми, където изрично бяха извършени премествания и добавяния или изваждания. Първите 16-битови микропроцесори, като 8086 и 68000, позволяват умножение и деление в хардуера, но процедурите са били изключително бавни: на 8086, например, е необходимо около 150, за да се раздели 32-битово число на 16 -битово число. кърлежи.

Нека първо разгледаме най-простата процедура за оптимизиране за умножение. За да умножите число по степен две, просто трябва да го преместите наляво с необходимия брой двоични (битови) позиции. Например, така изглежда някаква обща, но бавна последователност от команди при умножаване на стойността на променливата myvar по 8:

На процесори 8086/88 тази програма може да бъде преобразувана в по-бърза последователност от смени:

или дори това: shl myvar, 1 shl myvar, 1 shl myvar, 1

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

Но не бързайте - дори тази проста оптимизация не е толкова тривиална, колкото звучи! Опашката от инструкции в 80x86 процесори, конкретният модел процесор, използван във вашия компютър, и наличието или липсата на кеш памет могат да изиграят най-странните шеги. В някои случаи и на някои модели процесори понякога е възможно да се използва тази команда в опцията "смяна на броя позиции, посочени в CX":

А в процесора 80186 и по-късно има опция "изместване по броя на позициите, посочени от непосредствения операнд", което е още по-удобно:

Ако трябва да умножите по степен от две числа, по-дълги от 16 бита, използвайте флага за носене, за да организирате операции върху два или повече регистри. Например, за да умножите 32-битово число в DX: AX по 4, можете да напишете:

Чрез креативно комбиниране на смени и умножения можете да организирате бързо умножение с почти всяка конкретна стойност. Следният фрагмент умножава стойността в AX регистъра по 10:

Използването на отказ от гъвкавост за разделяне е малко по-ограничено. Разделянето на степента на две е, разбира се, много просто. Просто премествате номера надясно, като следите избора на първоначалната команда за смяна за желания тип разделение (подписан или неподписан). Например, за да извършите неподписано деление на 4 върху съдържанието на AX регистъра, можете да напишете: