А.В. Ахо, М.С. Лам, Р. Сети, Дж. Д. Ульман - Компиляторы - принципы, технологии и инструментарий (1114947), страница 9
Текст из файла (страница 9)
Время компиляции должно оставаться достаточно небольшим, чтобы поддерживать быстрый цикл разработки и отладки. Выполнить это требование становится все проще с ростом быстродействия машин. Зачастую сначала программа разрабатывается и отлаживается без оптимизации. Это делается не только для снижения времени компиляции, но и, что более важно, потому что неоптимизированную программу легче отлаживать (оптимизация компилятором зачастую ухудшает связь между исходным и объектным кодами). Включение оптимизации компилятора иногда выявляет новые проблемы в исходной программе; таким образом, тестирование должно выполняться для оптимизированного кода заново.
Требование дополнительного тестирования иногда удерживает от использования оптимизации в приложениях, в особенности если их производительность не критична. Глава 1. Введение в компиляцию И наконец, компилятор представляет собой сложную систему, которая при этом должна быть достаточно простой, чтобы стоимость ее разработки и поддержки оставалась в разумных пределах.
Имеется бесконечное число оптимизаций, которые могут быть реализованы, но для получения корректной и эффективной оптимизации часто требуются выходящие из ряда вон усилия. Приоритет в реализации отдается тем оптимизациям, которые приводят к большему выигрышу для встречающихся на практике исходных программ. Таким образом, при изучении компиляторов следует не только научиться строить компилятор, но и освоить общую методологию решения сложных и не ограниченных определенными рамками задач. Подход, используемый при разработке компиляторов, включает как теорию, так и эксперимент.
Обычно работа начинается с постановки задачи, основанной на интуитивном представлении о важности тех или иных вопросов. 1.5 Применения технологий компиляторов Многие программисты используют методы, с которыми они познакомились при изучении компиляторов, не только для написания компиляторов или каких-то их частей. Эти методы находят широкое применение и в других областях. Проектирование компиляторов затрагивает ряд других областей информатики, и в этом разделе мы рассмотрим некоторые наиболее важные взаимодействия и приложения компиляторных технологий. 1.5.1 Реализация высокоуровневых языков программирования Высокоуровневый язык про|раммирования определяет программную абстракцию: программист выражает алгоритм с использованием языка, а компилятор должен транслировать эту программу в целевой язык. Вообще говоря, высокоуровневые языки программирования проще для программирования, но менее эффективны, т.е.
целевые программы работают более медленно. Программисты, использующие низкоуровневые языки, обладают большими возможностями контроля над выполняемыми вычислениями и могут, в принципе, получать более эффективный код. К сожалению, на низкоуровневых языках труднее писать программы, и, что еще хуже, эти языки менее переносилиы, программы на иих более подвержены ошибкам и их труднее сопровождать. Оптимизирующие компиляторы включают технологии для повышения производительности генерируемого кода, тем самым компенсируя неэффективность высокоуровневых абстракций.
Пример 1.2. Ключевое слово геаЫег в языке программирования С представляет собой пример взаимодействия между методами компиляции и эволюцией языка. !.5. Применения технологий компиляторов 49 Когда язык С создавался в средине 1970-х годов, считалось необходимым позволить программисту определять, какие переменные должны размещаться в регистрах. Однако после разработки эффективных методов распределения регистров такое управление стало излишним, и большинство современных программ не используют эту возможность языка. Более того, программы, использующие ключевое слово ген!ятег, могут проигрывать в эффективности, поскольку программисты — не лучшие знатоки в низкоуровневых вопросах наподобие распределения регистров. Выбор оптимального распределения регистров очень сильно зависит от архитектуры конкретной машины.
Следование принятым программистом решениям о низкоуровневом распределении ресурсов может в действительности привести к падению производительности, в особенности при работе программы на других машинах, а не на той, для которой она создавалась. а Многие распространенные языки программирования смещаются в сторону увеличения уровня абстракции. Доминирующим языком программирования в !980-е годы был С, но уже в 1990-х годах во многих новых проектах использовался язык программирования С++; разработанный в 1995 году язык программирования 3ача быстро приобрел популярность к концу 1990-х годов. Новые возможности языков программирования приводили к новым исследованиям в области оптимизации кода.
Далее будут рассмотрены основные возможности языков программирования, которые стимулировали значительное развитие технологий компиляции. Практически все распространенные языки программирования, включая С, Рог!гав и СоЬо1, поддерживают определяемые пользователем агрегированные типы данных, такие как массивы и структуры, а также высокоуровневые средства управления потоком выполнения, такие как циклы и вызовы процедур. Если ограничиться непосредственной трансляцией каждой высокоуровневой конструкции или операции обращения к данным, результат получится очень неэффективным.
Разработанная оптимизация потоков Донных (дага-!!ои' орйппга!юп) анализирует потоки данных в программе и удаляет избыточность таких конструкций. Она генерирует кол, который напоминает код, написанный на низком уровне профессиональным программистом. Объектная ориентированность впервые появилась в языке Б!пш!а в 1967 году и вошла в такие языки, как Яша!1!а!к, С++, СФ и заиа. Ключевыми идеями, лежащими в основе объектной ориентированности, являются 1) абстракция данных; 2) наследование свойств. Они делают программы более модульными и упрощают их поддержку.
Объектно-ориентированные программы отличаются от программ, написанных на других Глава 1. Введение в компиляцию языках программирования, тем, что они состоят из большего количества процедур меньшего размера (которые в объектно-ориентированном программировании называются методами). Таким образом, оптимизация должна быть способна перешагнуть границы процедур в исходной программе. Здесь оказалось особенно полезным встраивание процедур, представляющее собой замену вызова процедуры ее телом. Была также разработана оптимизация, ускоряющая диспетчеризацию виртуальных методов.
)ача обладает многими облегчающими программирование возможностями, которые появились ранее в других языках программирования. Язык 3ача безопасен с точки зрения типов, т.е. объект не может использоваться вместо объекта несвязанного типа. Обращение к массивам выполняется с проверкой выхода за пределы массива. )ача не имеет указателей и не использует соответствующую арифметику. Этот язык программирования имеет встроенную систему сборки мусора, которая автоматически освобождает выделенную для переменных память, которая больше не используется.
Все эти возможности упрощают программирование, но приводят к дополнительным накладным расходам времени выполнения. Разработанная для языка программирования 1ача оптимизация снижает накладные расходы, например, устраняя излишние проверки диапазонов и выделяя память для объектов, недоступных извне процедуры, в стеке, а не в куче. Разработаны также эффективные алгоритмы для минимизации накладных расходов по сборке мусора. Кроме прочего, язык программирования Зача разработан для поддержки переносимости и мобильного кода. Программы распространяются в виде байт-кода )ача, который либо интерпретируется, либо компилируется в машинный код динамически, т.е. в процессе выполнения. Динамическая компиляция изучалась также и в других контекстах, когда информация извлекается динамически, во время выполнения, и используется для получения более оптимизированного кода.
При такой динамической оптимизации важно минимизировать время компиляции, которое является частью накладных расходов выполнения программы. Распространенная методика состоит в компиляции и оптимизации только тех частей программы, которые будут часто выполняться. 1.5.2 Оптимизация для архитектуры компьютера Быстрое развитие архитектур вычислительных систем привело к развитию новых технологий компиляции. Практически все высокопроизводительные системы используют преимущества двух основных технологий: параллелизм (рага11е!ып) и иерархии памяти.
Параллелизм можно найти на нескольких уровнях: на уровне команд, когда одновременно могут выполняться несколько команд, н на уровне процессора, когда различные потоки одного приложения выполняются на разных процессорах. Иерархии памяти представляют собой ответ на основное ограниче- !.5. Применения технологий компиляторов 51 ние, накладываемое на память: можно иметь очень быструю или очень большую память, но нельзя иметь память одновременно и большую, и быструю. Параллелизм Все современные микропроцессоры используют параллелизм на уровне команд, однако этот параллелизм может быть скрыт от программиста. Программы пишутся так, как если бы все их команды выполнялись последовательно; аппаратное обеспечение динамически проверяет зависимости в потоке последовательных команд и по возможности выполняет их параллельно.