
- •Семестр 1. Операционные системы
- •Введение
- •История развития ОС
- •Классификация ресурсов
- •Понятие операционной системы
- •Понятие процесса и потока
- •Дескриптор процесса
- •Диспетчеризация процессов
- •Механизмы взаимного исключения
- •Алгоритм Деккера
- •Типовые механизмы синхронизации
- •Поддержка механизмов синхронизации в UNIX
- •Механизмы межпроцессного взаимодействия
- •Барьеры
- •Обработка тупиковых ситуаций
- •Предотвращение взаимоблокировок
- •Управление памятью
- •Инвертированные таблицы страниц
- •Алгоритмы замещения страниц
- •Подсистемы ввода-вывода
- •Загрузка ОС
- •Загрузка ОС
- •Понятие файловой системы
- •Реализация каталогов
- •Совместно используемые файлы
- •Непротиворечивость файловой системы
- •Реализации файловых систем
- •HPFS
- •NTFS
- •Семестр 2. Теория компиляторов
- •Современные системы программирования
- •Литература
- •Формальные языки и грамматики
- •Форма Бэкуса-Наура
- •Классификация грамматик
- •Лексический анализ и регулярные грамматики
- •Лексический анализ
- •Конечные автоматы
- •Минимизация КА
- •КС-грамматики. Приведение КС-грамматик
- •НФ Хомского
- •НФ Грейбах
- •Алгоритмы построения синтаксических анализаторов
- •Синтаксический распознаватель с возвратом
- •Распознаватели без возвратов
- •Формальное определение
- •Ситуация
- •Грамматики предшествования
- •Построение матрицы предшествования
- •Построение матрицы предшествования
- •Генерация кода. Распределение памяти
- •Распределение памяти
- •Дисплей памяти процедуры
- •Синтаксическое дерево
- •Верификация и оптимизация кода
- •Ассемблеры и компоновщики
Верификация и оптимизация кода
Методы оптимизации кода. Свертка выражений. Оптимизация линейного участка. Свертка объектного кода. Оптимизация передачи параметров. Оптимизация циклов. Машинно-зависимые методы оптимизации. Методы анализа свойств корректности программ. Автоматизация верификации
В большинстве случаев генерация кода выполняется не для всей программы в целом, а последовательно для отдельных ее конструкций. При этом связи между фрагментами в полной мере не учитываются. В итоге код результирующей программы может содержать лишние команды и данные. Поэтому большинство современных компиляторов выполняют еще один необязательный этап – оптимизацию результирующей программы. Оптимизация нужна, поскольку результирующая программа строится не сразу, а поэтапно. Оптимизация – это обработка, связанная с переупорядочиванием и изменением операций в компилируемой программе с целью получения более эффективной, в некотором смысле, результирующей объектной программы. Как правило оптимизация использует два критерия эффективности – размер и скорость. В большинстве случаев увеличение скорости приводит к увеличению размера и наоборот. Кроме того, невозможно построить код программы, который был бы самым быстрым или самым коротким кодом результирующей программы, эквивалентной исходной. Для современных систем оптимизация может привести к увеличению быстродействия (уменьшению объема) в среднем на 10-30%. Различают два вида оптимизирующих преобразований:
-преобразования исходной программы, не зависящие от результирующего объектного языка
-преобразования результирующей объектной программы.
Первый вид преобразований не зависит от архитектуры системы. Второй – зависит не только от свойств объектного языка, но и от архитектуры системы. Используемые методы оптимизации не должны приводить к изменеиию смысла программы. Для преобразований первого вида это легко соблюдается. Преобразования второго вида могут вызвать проблемы, поскольку не всегда они имеют теоретическое обоснование и доказательство. Оптимизация может выполняться для следующих типов синтаксических конструкций: линейных участков программы, логических выражений, циклов, вызовов процедур и функций, и др.
Оптимизация линейных участков. Линейный участок программы – выполняемая по порядку последовательность операций, имеющая один вход и один выход. Ни одна операция линейного участка не может быть пропущена либо выполнена большее число раз, чем остальные операции данного линейного участка. Для линейных участков могут выполняться следующие виды оптимизации: удаление бесполезных присваиваний, исключение лишних вычислений, свертка операций объектного кода, перестановка операций, арифметические преобразования.
Удаление бесполезных присваиваний основано на том, что если в составе линейного участка имеется операция присваивания некоторой переменной А с номером i, и операция присваивания той же переменной А с номером j, j>i, и ни в одной операции между i и j значение переменной А не используется, то операция присваивания с номером i является бесполезной. В общем случае бесполезными могут оказаться не только операции присваивания, но и любые иные операции линейного участка, результат выполнения которых нигде не используется.
Исключение избыточных вычислений опирается на обнаружение и удаление из объектного кода операций, которые повторно обрабатывают одни и те же операнды. Операция с номером i считается лишней, если существует идентичная ей операция с номером j, j<i и никакой операнд, обрабатываемый этой операцией, не изменяется никакой операцией с номером между i и j.
Свертка объектного кода – это выполнение во время компиляции тех операций исходной программы, для которой значения операндов уже известны.. Например, вычисление выражения, все операнды которого являются константами.
Перестановка операций заключается в изменении порядка следования операций, которое может повысить эффективность выполнения, но не повлияет на результат. Например: 2*В*С*3 можно представить как (2*3)*В*С. Например, выражение, (В+С)+(Р+А) может потребовать память для хранения промежуточного результата. Перестановка операций в виде В+(С+(Р+А)) скорее всего обойдется без этого.
Арифметические преобразования представляют собой выполнение изменения характера и порядка следования операций на основе известных алгебраических и логических тождеств. Например, В*С+В*А = В*(С+А).
Оптимизация вычисления логических выражений основано на том, что не всегда необходимо полностью выполнять вычисление для того, чтобы определить его результат. Операция называется предопределенной для некоторого значения операнда, если ее результат зависит только от этого операнда и остается неизменным относительно значений других операндов. Например, операция OR предопределена для значения операнда True, а операция AND – для значения операнда false. Однако иногда такие преобразования не инварианты к смыслу программы. Например A OR F(B) если результат предопределен относительно значения А, F(B) не будет выполняться. Однако если функция помимо возвращения значения (расчета) выполняла побочные действия, например, изменяла значения глобальных переменных и т.д., то результат выполнения программы может измениться. Существуют и арифметические операции, которые предопределены для некоторого значения. Например, умножение на 0. Операция называется инвариантной относительно некоторого значения операнда, если ее результат не зависит от этого значения операнда и определяется другими операндами. Оптимизация может включать и исключение вычислений для инвариантных операндов.
Оптимизация передачи параметров в процедуры и функции. Передача параметров через стек является неэффективной, если процедура или функция выполняет несложные вычисления над небольшим количеством параметров. В результате код размещения параметров в стеке и освобождения стека по выходу из процедуры может занимать значительную долю операций такой функции. Используют два подхода: передача параметров через регистры процессора и подстановка кода функции непосредственно в место вызова в объектном коде (inline функции). Передача параметров через регистры имеет тот недостаток, что зависит от архитектуры системы. Кроме того, таким образом соптимизированные функции не могут использоваться в качестве библиотечных. Использование inline функций ускоряет обработку, но увеличивает размер кода.
Оптимизация циклов. Чтобы обнаружить все циклы в исходной программе, используются методы, основанные на построении графа управления программы. При оптимизации циклов используют: вынесение инвариантных вычислений за пределы цикла; замена операций с индуктивными переменными; слияние и развертывание циклов. В первом случае, за пределы цикла
выносятся те операции, операнды которых не изменяются в процессе выполнения цикла. Переменная называется индуктивной в цикле, если ее значения в теле цикла образуют арифметическую прогрессию. В простейшем случае, это может быть замена умножения на счетчик цикла сложением. Например: for(S=10, i=1; i<=N; i++) A[i]=i*S; можно заменить на
for(S=10, i=1; i<=N; i++, S+=10) A[i]=S;
Слияние и развертывание циклов предусматривает слияние двух вложенных циклов в один и замену цикла линейной последовательностью операций. Например: for(i=1;i<=N;i++) for(j=1;j<=M;j++) A[i][j]=0; успешно заменяется циклом: for(X=M*N, i=1; i<=X; i++) A[i]=0; Развертывание циклов можно выполнить для тех из них, кратность выполнения которых известна уже на этапе компиляции.
Машино-зависимые методы оптимизации. Они ориентированы на конкретную архитектуру системы. Например, использование регистров общего назначения для хранения значений операндов и результатов вычислений увеличивает быстродействие.Или, например, не все операции могут быть выполнены с операндом в памяти и требуют предварительной загрузки в регистр, иногда жестко определенный, процессора. Поскольку количество программно доступных регистров ограничено, встает вопрос об их распределении. Здесь может возникать ситуация, аналогичная подкачке страниц в память – например, надо загруить переменную в регистр, а все доступные регистры уже заняты. Какой из них выгрузить в память? Ряд процессоров и систем позволяют параллельное выполнение операций. Можно строить компилятор таким образом, что по соседству будет максимальное количество операций, операнды которых не зависят друг от друга. Конечно, в челом это не решаемая пока задача, однако для конкретного оператора решение заключается в в порядке выполнения операций.
Свойство программы, характеризующее отсутствие в ней ошибок по отношению к целям разработки, называют корректностью программы. Корректность программы, как формальную, так и смысловую, нужно доказать. Задача доказательства синтаксической правильности решена благодаря описанию синтаксиса языка на основе теории формальных грамматик. Естественно возникает вопрос: можно ли аналогично решить и задачу семантической корректности? В этом случае системы программирования стали бы качественно новыми системами – системами доказательного программирования. Традиционно семантическая корректность проверяется путем тестирования программы. Тестирование – процесс выполнения программы с целью обнаружения ошибок. Выполнение всестороннего тестирования сложной программы практически невозможно. В то же время корректность программы имеет смысл только при четко сформулированной цели разработки. Формализованное описание постановки задачи называется спецификацией задачи. Верификация – процесс доказательства соответствия между программной реализацией и спецификацией задачи. Верификация фактически представляет собой аналитическое исследование свойств программы по ее тексту, т.е. без выполнения самой программы. Наиболее распространенным методом доказательства частичной корректности программ является метод индуктивных утверждений. Он позволяет свести анализ свойств программы к доказательству конечного числа утверждений, записанных в виде формул логического языка спецификации и имеющих интерпретацию в проблемной области решаемой задачи. При выполнении программы с различными вариантами исходных данных возможна реализация различных цепочек операторов. Такие цепочки называются трассами вычислений. При наличии в программе повторяющихся вычислений перебор всех трасс обычно оказывается невозможен. Для решения этой проблемы вводят инварианту цикла – утверждение, приписанное циклу, которое должно сохранять истинное значение при каждом выполнении тела цикла. Доказательство правильности программы, основанное на применении метода индуктивных утверждений, содержит следующие основные этапы:
-построение схемы алгоритма решения задачи
-формулировка утверждений для входаи выхода программы в виде формул логического языка спецификации
-выявление всех циклов и формулировка контрольного утверждения для каждого из них на логическом языке спецификации
-составление списка путей между контрольными точками алгоритма
-построения условия верификации для каждого пути с использованием семантики операторов, образующих путь
-доказательство истинности всех условий верификации как теорем формальной теории проблемной области решаемой задачи
-доказательство завершения программы.
Исследования по формализации семантики начались с середины 60-х. выработаны три основных подхода: операционный, аксиоматический и денотационный. Наиболее практичным считается аксиоматичный подход, предложенный Хоаром. В настоящее время применение ПК для верификации программ идет преимущественно по пути создания систем верификации, предусматривающих диалог с оператором на некоторых этапах работы. На первом этапе выполняется анализ программы, т.е. контроль лексической и синтаксической правильности аннотированной программы. Программа переводится на промежуточный язык. Далее происходит генерация условий верификации. Она основана на применении аксиоматической системы используемого ЯП и осуществляется без участия оператора. Далее выполняется доказательство условий верификации. При этом могут выполняться некоторые эквивалентные преобразования и упрощения условий верификации. При необходимости возможно применение системы аксиом пользователя. Результаты доказательства исследуются анализатором доказательства. Возможны следующие ситуации:
-все условия верификации истинны, завершение работы
-доказательство отдельных условий не завершено. Эти условия возвращаются на доказательство с применением дополнительной информации, вводимой пользователем
-среди условий верификации обнаружены ложные. Ошибки могут быть как в спецификации программы, так и в операторах самой программы, формально различить эти ситуации невозможно. Пользователь должен выполнить модификацию аннотированной программы и повторить процедуру обработки.