Генерация высококачественного кода для программ на СИ - Методы оптимизации. Часть 2

ОГЛАВЛЕНИЕ


 "Снижение мощности" подразумевает замещение операций, которые требуют
 большего времени выполнения, более быстрыми. Компилятор может применять
 снижение мощности несколькими способами. Например, применяя снижение
 мощности к сгенерированному коду, компилятор может подменять операции,
 которые умножают или делят целые числа на степени двойки, операциями
 сдвига.
 "Удаление недостижимого кода" - еще один метод оптимизации. Недостижимый
 код - это некоторая последовательность инструкций программы, которая
 недостижима ни по одному пути в программе. Он может образоваться как
 следствие предыдущих операций оптимизации, кода условной отладки, или
 частых изменений программы многими программистами. Следующие операторы -
 это вариант кода для проверки компилятора на выполнение этого метода
 оптимизации.
  #define DEBUG 0
  if(DEBUG)
       printf("Debug Function\n");

 Манифестные константы часто могут скрывать существование недостижимого
 кода, особенно если такой код определяется внутри включаемого
 файла-заголовка.
 "Удаление лишних присваиваний" включает нахождение промежутка жизни
 переменной и удаление присваиваний этой переменной, если эти присваивания
 не могут изменить логику программы. Этот метод освобождает ограниченные
 ресурсы, такие как пространство стека или машинные регистры. В следующей
 последовательности команд:
  a = 5;
  b = 0;
  a = b;

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

 Цель "распределения переменных по регистрам" состоит в попытке обеспечить
 оптимальное назначение регистров путем сохранения часто используемых
 переменных в регистрах так долго, как это возможно, для того, чтобы
 исключить более медленный доступ к памяти. Количество регистров, доступных
 для использования, зависит от архитектуры процессора. Семейство
 микропроцессоров Intel 80x86 резервирует много регистров для специального
 использования и имеет несколько универсальных регистров. В помощь
 распределению переменных по регистрам язык Си предоставляет спецификатор
 класса регистровой памяти, который дает возможность программисту
 указывать, какие переменные должны располагаться в регистрах.
 При назначении переменных регистрам компилятор принимает во внимание не
 только какие переменные нужно выделить, но также и регистры, которым они
 назначаются. Выбор переменных зависит от частоты их использования,
 промежутков жизни текущих регистровых переменных (которые определяются при
 анализе потоков данных) и количества доступных регистров. В зависимости от
 степени выполняемой компилятором оптимизации промежуток жизни переменной
 может определяться внутри единственного оператора, внутри базового блока
 или перекрывать несколько базовых блоков. Переменная сохраняется в
 регистре только если она будет снова использоваться. Если на переменную в
 дальнейшем не будет ссылок, то она сохраняется в оперативной памяти,
 освобождая регистр для другой переменной.
 Поскольку оптимизирующему компилятору известен промежуток жизни
 переменной, он не будет намеренно генерировать "лишние операции сохранения
 и загрузки" (регистров). Лишние операции сохранения удаляются посредством
 удаления излишних присваиваний; лишние операции загрузки опускаются с
 помощью усовершенствованного распределения переменных по регистрам. Имея
 текст:
 a = i + 2;
 b = a + 3;
 компилятор без возможностей оптимизации может сгенерировать следующий код:

  mov AX,i
  add AX,2
  mov a,AX
  mov AX,a
  add AX,3
  mov b,AX

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