Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Layt_teor_osnovy_form_yazykov.doc
Скачиваний:
2
Добавлен:
01.04.2025
Размер:
1.17 Mб
Скачать

5.6. Оптимизация кода.

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

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

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

Таким образом, термин “оптимизация” совершенно неправильный — на практике мы должны довольствоваться улучшением кода. На разных стадиях процесса компиляции применяются различные приемы улучшения кода.

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

Проведем преобразования, с помощью которых можно сделать код более коротким:

(1) Если + — коммутативная операция, можно заменить последовательность команд вида

MOV AX, A

ADD AX, B

последовательностью MOV AX, A+B

(2) Подобным же образом, если * — коммутативная операция, можно заменить

MOV AX, A

MUL AX, B

последовательностью MOV AX, A*B

Пример 5.4. Эти преобразования выбраны из-за их применимости к рассмотренному ранее примеру. Вообще таких преобразований много и надо пробовать применять их в разных комбинациях.

MOV AX, PRICE+TAX

MUL AX, 30

MOV COST, AX

5.7. Анализ и исправление ошибок.

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

Компилятор имеет возможность обнаруживать ошибки в программе, по крайней мере, на трех этапах компиляции: во время лексического анализа, синтаксического анализа, при генерации кода. Если встретилась ошибка, то компилятору трудно по неправильной программе решить, что имел в виду ее автор. Эта задача граничит с приложениями искусственного интеллекта. Но в некоторых случаях легко сделать подходящее предположение.

Например, если исходный оператор выглядит как А=В*2С, то вполне правдоподобно, что подразумевалось А = В*2*С.

Вообще в тех случаях, когда процесс разбора доходит до того места во входной цепочке, где он не может дальше правильно продолжаться, некоторые компиляторы стараются произвести “минимальное” изменение во входной цепочке, чтобы продолжить разбор. Перечислим несколько возможных изменений:

(1) Замена одного знака. Например, если лексический анализатор выдает синтаксическому анализатору “идентификатор” INTEJER в неподходящем для появления идентификатора месте программы, то компилятор может догадаться, что подразумевалось зарезервированное слово INTEGER.

(2) Вставка одной лексемы. Например, компилятор может заменить 2С на 2*С (2+С тоже годится, но в данном случае мы “знаем”, что 2*С правдоподобнее).

(3) Простая перестановка лексем. Например, вместо I: INTEGER может быть неправильно написано : INTEGER I.

Во многих языках программирования операторы легко опознаются. Если разбор конкретного, неправильно построенного оператора становится безнадежным — даже после того, как произведены изменения вроде описанных выше, часто можно полностью игнорировать этот неправильный оператор и продолжить разбор, как будто его и не было.

Однако следует отметить, что в случае когда возможны исправления, ошибка должна быть однозначно определена. Например, в случае ошибки в операторе IF (I>3) THENN I:=5; есть следующие возможные варианты исправления:

  1. IF (I>3) THEN NI:=5; если NI – идентификатор;

  2. IF (I>3) THEN I:=5; если ошибка в зарезервированном слове.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]