5. Оптимизация программ Всякое преобразование, включенное в транслятор,
5. Оптимизация программ
Всякое преобразование, включенное в транслятор, должно сохранять семантику программы. Это достижимо при выполнении эквивалентных преобразований. Однако проблема определения эквивалентности или не эквивалентности двух схем программ в общем случае неразрешима. Поэтому набор оптимизирующих преобразований строится на эвристической основе, исходя из класса решаемых задач, уровня понимания и существующей техники анализа и преобразования программ.
В общем случае требуется, чтобы преобразование программы A в B было выполнено корректно. Т.е. если программа A выполнима на некотором наборе данных, то и программа B должна быть выполнена на том же наборе данных и давать тот же результат.
1. Некоторые оптимизирующие преобразования 1) Разгрузка участков повторяемости В этом случае выполняется вынесение вычислений из многократно проходимых участков программы на участки редко проходимые. (Степень повторяемости разных участков существенно различается. Как правило часто повторяемые участки занимают в программе мало места, но тратят много времени.)
К этому способу относятся чистки циклов, тел рекурсивных процедур. В процессе оптимизации инвариантные (неизменные) на данном участке повторяемости выражения выносятся и помещаются перед входом в участок повторяемости – чистка вверх, или за участок – чистка вниз.
Примеры 1. Вычислить y = ( x + 1) n for ( i = 0, y = 1; i
Пример 2. // посчитать количество * в строке s for( i =0, k = 0 ; i < strlen(s); i++) if (s[i] == ’*’)k++; чистка вверх z = strlen(s); z
2) Упрощение действий Этот способ оптимизации предполагает замену группы действий (возможно удаленных друг от друга) на группу других действий, дающих тот же результат с точки зрения всей программы, но имеющих меньшую сложность.
а) удаление или замена меньшим числом индуктивных переменных или выражений. Индуктивные величины – это величины с регулярным изменением от шага к шагу ( например, по закону арифметической или геометрической прогрессии).
Примеры 1) h = ( b – a ) / n; for( i = 0; i<=n; i++) { x = a + i *h; … y = f(x); … } x, i - индуктивные переменные Заменяем цикл на for ( x = a;x<=b; x+=h) { y = f(x); … } Осталась одна индуктивная переменная x
2) Вычисляется сумма элементов главной диагонали матрицы for( i = 0, j = 0; i
3) Вычислить сумму элементов с четным индексом for (s = 0, i = 0; i
б) уменьшение силы операций; Умножение заменяется сложением (см п.3), возведение в степень – умножением и др.
в) удаление лишних выражений и подвыражений(экономия подвыражений) Выполняется их замена на переменную, хранящую значение такого выражения. Примеры. if (x>2) y= (x-2)*(x-2); else z = abs(x-2); y = (a+b)*(a+b)*(a+b); (см. п. 2.2)
г) использование алгебраических законов для сокращения числа операций. Например, коммутативности a b = b a, ассоциативности a (b c ) = (a b ) c ( +, *), дистрибутивности: операция 1 дистрибутивна к операции 2 , если a 1 ( b 2 c) = ( a 1 b) 2 ( a 1 c) ( * дистрибутивна к +)
Пример 2 1 5 3 4 7 6 a * (b * c) + ( b * a) * d + a * e = ( a * b ) * c + ( a * b ) * d + a * e = ( a * b ) * ( c + d ) + a * e = a * (b * ( c + d ) + a * e = a * ( b * ( c + d ) + e ) 4 3 1 2 ассоц комм дистр ассоц дистр е) замена рекурсии циклом и др.
3. Реализация действий Это способ повышения качества программы за счет выполнения некоторых вычислений на этапе трансляции: а) свертка констант (п.2.1) Выполняется вычисление выражений с константными операндами.
Примеры const int n = 20, k = 5; …………………… for ( i = 0; i< n – k ; i++) { … } i = 2; j = i + 5; 15 // заменяется на j = 7 7
б) Ликвидация константных распознавателей Выполняется замена условного оператора на его вариант, если условие константа. Например, const int k = 10; …………… if ( k >0) y = z/k; else y = z*k ; заменяется на y = z/k;
в) втягивание констант Это замена выражения на другое путем удаления подвыражений с константно- тождественным значением. Пример 1. k = 1; … y = k* (x – a) * b; заменяется на y = ( x – a) * b;
Такие подвыражения называются нейтральными. Определение. Выражение нейтрально относительно бинарной операции , если = = , для любого . а) 0 нейтрально для + б) 1 нейтрально для * в) false нейтрально для дизъюнкции | г) true нейтрально для &
Пример 2 . const int k = 5, n = 6; z = ( n – k) * a * b; заменится на z = a * b
г) распроцедуривание Замена вызова процедуры (функции) телом процедуры(функции), если это небольшие процедуры. Вызов обходится дороже. ( в С++ явно можно задать такие функции,как inline).
4. Чистка программ Выполняется удаление ненужных объектов и конструкций: а) удаление операторов, недостижимых по управлению из начала. Например, …..goto 3; x = a; y = y*x; 3: z = x+y ;
б) удаление неиспользуемых операторов, вычисляющих т.н. мертвые переменные.(dead code elimination) Определение. Переменная x жива в некоторой точке программы, если из этой точки существует путь до какого-либо использования переменной x, не содержащий операторов, задающих x новое значение. Если такого пути нет, то x является мертвой. Пример. ..x = a ; y = z - 1; x = 5; r = x + y; мертва
в) удаление функций и переменных, к которым нет обращений;
5. Экономия памяти Уменьшение объема памяти для информационных объектов. Глобальная экономия - это совмещение памяти для переменных, не используемых одновременно. Это, в частности, достигается использованием стека для локальных переменных в функциях.
C++ В С++ данную экономию памяти может выполнять программист, используя тип данных union union data{ char s[3]; float x; int b; } a; Для экономии памяти также используется и оператор delete. 4 байта
6. Сокращение программы Это оптимизация длины самой программы. Многие описанные выше преобразования приводят к сокращению длины. Запроцедуривание – выделение похожих фрагментов в функцию. Критерий времени – первостепеннее.
2. Оптимизация в пределах линейных участков Линейный участок - это последовательность операций с одним входом и одним выходом (первая и последняя операции). Причем все операции выполняются последовательно и внутри участка нет переходов. На линейных участках выполняются обычно 2 оптимизации – свертка и устранение лишних операций.
Внутреннее представление В процессе оптимизации используется внутреннее представление в виде триад. Это конструкция вида операция операнд1, операнд2 Причем, если операнд – результат какой- то триады, то он задается в виде (i), где i – номер триады-результата. Это называется ссылкой на триаду.
2.1 Свертка Метод предложен Флойдом. Как было упомянуто в п.1, свертка – это выполнение операций во время компиляции, если значения операндов известны.
Рассмотрим пример. Триады (2) и (4) могут быть вычислены и удалены.
Таблица T Для выполнения свертки формируется таблица T, содержащая пары (a, k), где a – простая переменная, а k – ее текущее значение. Каждая свертываемая триада заменяется новой вида (C k, пусто), означающей, что в этом месте не требуется генерации команд, а k – значение (результат) триады.
Алгоритм свертки Алгоритм последовательно просматривает каждую триаду линейного участка и выполняет для каждой триады (если возможно) следующее: Если операнд – переменная из таблицы T, то он заменяется на соответствующее значение k (из строки (a, k)); 2. Если операнд – ссылка на триаду типа (C k, ), то он заменяется тоже на k;
3. Если все операнды являются константами, то операция может быть свернута. Данная триада вычисляется и вместо нее формируется триада (C k, ), где k – результат триады. 4. Если триада есть операция присвоения a = b, то а) если b – константа, то в таблицу T заносится строка (a, b) (причем старое значение a, если было, исключается); б) если b – не константа, то a со своим значением из таблицы T исключается.
i = 3 (1) = 3, i ш.4а) ( i, 3) (1) = 3, i j = i + 1 (2) + i, 1 (3) = (2), j ш1: + 3,1 ш3 (2) C 4, ш2:= 4,j ш4а) (j, 4) (3) = 4, j Триада вычисляется и сворачивается k = j + i (4) + j, i (5) = (4), k ш1: +3,4 (4) C 7, ш3 ш2:= 7,k ш4а) (k, 7) (5) = 7, k j = m (6) = m, j ш 4б (6) = m, j l = j + k (7) + j, k (8) = (7), l ш1:+ j, 7 (7) + j, 7 (8) = (7), l
2.2 Исключение лишних операций Определение. i-ая операция линейного участка считается лишней, если существует более ранняя идентичная j-ая операция и никакая переменная, от которой зависит эта i-ая операция, не изменяется третьей операцией, лежащей между i-ой и j-ой операциями.
Пример Для оптимизации также используется представление триадами. x = (a+b)*(a+b)*(a+b); (1) + a, b (2) + a, b - лишняя (3) * (1), (2) (4) + a, b - лишняя (5) * (3),(4) (6) = (5), x Их можно удалить!
Алгоритм просматривает операции в порядке их появления. Если i-ая триада лишняя, так как уже имеется идентичная ей j-ая, то она заменяется триадой ( Same j, ), где триада Same ничего не делает и не порождает никаких команд при генерации. Её назначение – ссылка на такую же триаду.
dependence Алгоритм вычисляет и использует числа зависимости переменных dep(a) и триад dep(i), которые определяются по правилу: 1) в начале линейного участка числа зависимости переменных полагаются равными 0( dep(a) = 0), так как их значение еще не зависит ни от одной триады; 2) После обработки i-ой триады, в которой переменной a присваивается некоторое значение, dep(a) = i, так как её новое значение зависит от i-ой триады; 3) При обработке i-ой триады её dep(i) = max(dep(операнд1), dep(операнд2)) + 1.
Порассуждаем Несложные логические рассуждения приведут к выводу, что i-ая триада лишняя, если есть такая же j-ая триада (j
Алгоритм исключения лишних операций Алгоритм для каждой i-ой триады делает следующее: 1. Если операнд ссылается на триаду вида (Same j, ), то он заменяется на (j); 2. Вычисляется dep(i) согласно 3) правила; 3. Если существует идентичная j-ая триада, j
Рассмотрим работу алгоритма для операций x = (a + b)*(a + b)*(a + b); a = c; y = a + b; 0 0 0 0 0 (1) + a, b ш2 1 (1) + a, b (2) + a, b ш2 1 ш3: dep(2)=dep(1) (2)same 1 (3) * (1),(2) ш1: (3) *(1),(1) ш2 2 (3) *(1),(1) (4) + a, b ш2 1 ш3: dep(4)=dep(1) (4)same 1 (5) * (3), (4) ш1: (5) *(3),(1) ш2 3 (5) *(3),(1)
0 0 0 0 0 (6) = (5), x ш2 4 3 ш4 6 (6) = (5),x (7) = c, a ш2 1 ш4 7 (7) = c, a (8) + a, b ш2 8 ш3: но dep(8)dep(1) (8) + a, b (9) = (8),y ш2 9 ш4 9 (9) = (8),y Триады (2), (4) при генерации будут удалены. x = (a + b)*(a + b)*(a + b); a = c; y = a + b;
Результирующие триады x = (a + b)*(a + b)*(a + b); a = c; y = a + b; (1) + a, b (2) same 1 (3)* (1), (1) (4) same 1 (5) (3), (1) (6) = (5),x (7) = c, a (8) + a, b (9) = (8),y
Самостоятельно. 1. d= d + c*b; a= d - c*b; c = d – c*b; 2. x = (a + b)*(a + b); b = d; y = (a + b)/2-c;
3. Оптимизация в циклах Рассмотрим 2 оптимизации, изложенные в книге Гриса «Конструирование компиляторов для ЦВМ». Оптимизации выполняются после синтаксического анализа за 3 прохода.
Программа анализа циклов Обработка инвариантных операций (чистка вверх ) Замена сложных операций (* на -) От синт. анализа К генерации кода Программа анализа циклов выявляет циклы, подлежащие оптимизации, и формирует информацию, которая используется далее. В дальнейшем будем рассматривать простые переменные целого типа vari или вещественного varf.
3.1 Внутреннее представление циклов и требования к ним для указанных оптимизаций Рассматривается следующий образец цикла for i = a step b to c do цикл Предполагается, что транслятор умеет распознавать различные элементы цикла i, a, b, c, тело цикла и конец цикла.
Последовательность команд для генерации цикла имеет вид до оптимизации : init : i = a; test : if (i>с)goto m; loop : тело_цикла incr : i = i + b; goto test; m:… после оптимизации: init : i = a;”начальные операции” test : if (i>с) goto m; loop : измененное_тело_цикла incr : i = i + b; “операции приращения” goto test; m:
Для реализации двух указанных оптимизаций должны выполняться следующие требования: 1. Переменная цикла i должна быть типа vari; 2. Выражение для шага b должно иметь тип vari и не должно содержать i. Операнды шага – константы и величины типа vari и varf; 3. В теле цикла не должно быть присвоений переменной цикла i или переменным выражения для шага b. Программа анализа циклов строит var-список – это список переменных, изменяемых в цикле.
3.2 Блок обработки инвариантных операций Определение. Операция является инвариантной в цикле, если ее операнды не входят в var-список этого цикла. Такие операции можно вынести в init- участок (это и есть чистка вверх). Обычно рассматриваются операции о = { +, *, -, / }.
Для оптимизации операции представляются тетрадами операция операнд1, операнд2, ri где ri – результат операции – рабочая переменная (например, регистр). Причем используются разные рабочие переменные для разных операций.
Алгоритм “чистки вверх” Алгоритм последовательно просматривает операции циклов во внутреннем представлении и Предположим, что встретили операцию о a, b, r1. Если a и/или b – не инвариантны в этом цикле, то последующие шаги обходятся, иначе на 2. 2. Если в init-участке уже есть подобная операция о a, b, r2 (может быть с другой рабочей переменной), то все r1 в цикле заменяем на r2 и переходим к шагу 4, иначе на 3;
3. Операция добавляется в init-участок; 4. Операция из цикла выбрасывается. r1 удаляется из var-списка. Исключение r1 из списка изменяемых переменных позволяет выносить из цикла последующие операции, содержащие r1 и другой неизменяемый операнд.
Пример. В курсе информатики на первом курсе разбиралась операция АЭМ – адрес элемента массива. Пусть задана матрица, в строке n элементов. Тогда элементу a[i][j] соответствует адрес a + i*n + j (для простоты будем считать, что элемент матрицы занимает 1 элемент памяти).
Пусть задан цикл for ( i = 0; i
init : i = 0; test: if (i>=n) goto m; loop: * i, n, r1 ->var-список + r1, j, r2 -> var -список + r3, i, r4 -> var -список + a[r2], a[r4], r5 -> var -список = r5,,a[r2] incr: i = i + 1; goto test; m: … a[i][j] = a [i][j] + a[j][i]; АЭМ = a + i*n + j j, n - инвариантны ш3-4.Переносим тетраду в init-участок. r3 из var-списка удаляем * j, n, r3 * j, n, r3 -> var -список Чистенько!
3.3 Блок замены сложных операций Рассматривается замена в циклах операции * на +. Алгоритм последовательно анализирует каждую операцию цикла. Встречая операцию * i, k, r1 или * k, i, r1 , где переменная k инвариантна в цикле, i – переменная цикла, алгоритм заменяет ее на пустую операцию, а в init-участок и incr- участок добавляет действия в соответствии со следующими соображениями.
При изменении переменной цикла i на шаг b, т.е. i = i + b, в операции * i, k, r1 будет изменение r1 = (i + b)*k = i*k + b*k = r1 + b*k. Так как k – инвариантна в цикле, а шаг b тоже содержит инвариантные величины (см. требования 2, 3 к циклам), то операцию и вычисление b*k можно вынести в init-участок, а вычисление r1 в incr-участок по операции +.
Алгоритм 1. Если в init-участке уже есть операция * i, k, r3 , т.е. такая же, но с другой рабочей переменной, то в цикле r1 заменить на r3, операцию * i, k, r1 из цикла удалить. Перейти к шагу 3. 2. Если нет, то в init-участок добавляется операция * i, k, r1, а из цикла удаляется. 3. Если в init-участке операции вида * b, k, rl нет, то она формируется – это приращение для r1 (такая операция могла появиться при выполнении чистки цикла). 4. В incr-участок добавляется операция + r1, rl, r1
Пример. Из предыдущего параграфа init : i = 0; r3 = j*n; test: if (i>=n) goto m; loop: * i, n, r1 + r1, j, r2 + r3, i, r4 + a[r2], a[r4], r5 = r5,,a[r2] incr: i = i + 1; goto test; m: … i – переменная цикла, n – инвариантна в цикле * i, n, r1 r6 = b*n=1*n ; + r1, r6, r1; 2. .. в init-участок добавляется операция * i, k, r1, а из цикла удаляется. 3. Если в init-участке операции вида * b, k, rl нет, то она формируется – это приращение для r1 4. В incr-участок добавляется операция + r1, rl, r1
3.4 Обсуждение некоторых моментов рассмотренных оптимизаций В приведенных выше оптимизациях циклов существенно, чтобы вынесение инвариантных операций работало первым. Например, пусть задано выражение (x + y) * i, где x и y – инвариантны в цикле. Тетрады для него имеют вид (1) + x, y, r1 (2) * r1, i, r2 Замена * в тетраде (2) невозможна, так как r1 – не инвариантна в цикле. Если же тетраду (1) ранее вынести из цикла, как инвариантную, то r1 будет исключена из var-списка и можно будет применить алгоритм замены * на +.
Изложенный метод замены сложных операций “не справляется” ( по словам Гриса) с выражениями типа (i + k)*x, где x и k – инвариантны, а i – переменная цикла (1) + i, k, r1 (2) * r1, x,r2 // r1 – не инвариантна
Выходы из положения: 1. Уметь раскрывать скобки (i + k)*x = i*x + k*x Тогда, init: loop: r3 = r1+r2 incr : i = i + b r1 = i* x r4 = b*x //приращение для r1 r2 = k*x r1 = r1 + r4 // по 3.2 r2 = k*x // по 3.3 r1 = i* x
(i + k)*x 2. Раскрытие скобок – сложная проблема. Другой подход, не раскрывая скобок. (1) r1 = i + k; (2) r2= r1*x; Во-первых, вынести (1) в init-участок, а в incr-участок - изменение r1 на тот же шаг b, что и i. Во-вторых, разрешить рабочим переменным участвовать в оптимизации, как переменной цикла i.
Таким образом, будем иметь 1 шаг: init : test: loop: r2 = r1 * x; incr : i = i + b; r1 = i + k; // r1 “причислим” к i r1 = r1 +b ; r1 = i + k; (i + k)*x- скобки не раскрываем 2 шаг. Разрешив r1 участвовать в оптимизации как переменной цикла(она меняется на тот же шаг), получим init : r1 = i + k; r2 = r1 * x; r3 = b *x; // приращение для r2 incr: i = i + b; loop: r1 = r1 +b ; r2 = r2 + r3; Лишнее!
Еще пример, как оптимизации существенно могут упростить цикл. Пусть в цикле вычисляется выражение d = (i * x + y ) * z; x, y, z – инвариантны в цикле init: i = a; test: loop: r1= i*x; r2 = r1 + y; r3 = r2*z; d = r3; incr: i = i + b; применим 3.3 r1 = i*x; r4 = b*x; //r1 r1 = r1 + r4; к r1 + y применим шаг 1(см. выше): при увеличении r1 на r4, r2 тоже увеличивается на r4. r2 = r1 + y; r2 = r2 + r4; к r3 применим 3.3, придав r2 статус переменной цикла(выше-шаг2) r3 = r2*z; r5 = r4*z; // r3 r3 = r3 + r5; init: i = a; test: loop: r1= i*x; r2 = r1 + y; r3 = r2*z; d = r3; incr: i = i+b; r3 = r3 + r5;
Вопросы по гл.4 Что такое идентификация? Что такое определяющее и использующее вхождения лексем? Как реализуется идентификация? Какие ошибки обнаруживаются при идентификации? Что такое атрибутная индукция? Приведите пример.
The end
д) объединение, расчленение или развертывание циклов Развертывание – преобразование, которое просто, но часто остается незамеченным (Ахо, стр 417) Пример. i = 1; a[i] = b[i]; i>= 100 -> стоп i = i+1; + - i = 1; a[i] = b[i]; i = i+1 a[i] = b[i] i>= 100 --> стоп i= i+1 на 50 сравнений меньше! + - е) замена рекурсии циклом и др.
init: i=a; test: loop: r1= i*x; r2 = r1 + y; r3 = r2*z; d = r3; incr: i = i+b;
33254-trans_5.ppt
- Количество слайдов: 73