trans_5.ppt
- Количество слайдов: 73
5. Оптимизация программ
Всякое преобразование, включенное в транслятор, должно сохранять семантику программы. Это достижимо при выполнении эквивалентных преобразований. Однако проблема определения эквивалентности или не эквивалентности двух схем программ в общем случае неразрешима. Поэтому набор оптимизирующих преобразований строится на эвристической основе, исходя из класса решаемых задач, уровня понимания и существующей техники анализа и преобразования программ.
В общем случае требуется, чтобы преобразование программы A в B было выполнено корректно. Т. е. если программа A выполнима на некотором наборе данных, то и программа B должна быть выполнена на том же наборе данных и давать тот же результат.
1. Некоторые оптимизирующие преобразования 1) Разгрузка участков повторяемости В этом случае выполняется вынесение вычислений из многократно проходимых участков программы на участки редко проходимые. (Степень повторяемости разных участков существенно различается. Как правило часто повторяемые участки занимают в программе мало места, но тратят много времени. )
К этому способу относятся чистки циклов, тел рекурсивных процедур. В процессе оптимизации инвариантные (неизменные) на данном участке повторяемости выражения выносятся и помещаются перед входом в участок повторяемости – чистка вверх, или за участок – чистка вниз.
Примеры 1. Вычислить y = ( x + 1) n z = x + 1; for ( i = 0, y = 1; i<n; i++) y = y*z; x+1 ); y* ( инвариатно в цикле чистка вверх
Пример 2. // посчитать количество * в строке s z = strlen(s); z for( i =0, k = 0 ; i < strlen(s); i++) чистка if (s[i] == ’*’)k++; вверх
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<n&&j<n; j++, i++) s = s + a[i ][ j ]; j – удаляем for( i = 0; i<n; i++) s = s + a[i ][ i ];
3) Вычислить сумму элементов с четным индексом for (s = 0, i = 0; i<n; i++) if ( i % 2 == 0) s= s + a[i]; индуктивное 010101… выражение Удаляем его. for ( s = 0, i = 0; i<n; i+=2) s = s + a[i];
б) уменьшение силы операций; Умножение заменяется сложением (см п. 3), возведение в степень – умножением и др.
в) удаление лишних выражений и подвыражений(экономия подвыражений) Выполняется их замена на переменную, хранящую значение такого выражения. Примеры. if (x>2) y= (x-2)*(x-2); else z = abs(x-2); y = (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++) 15 {…} i = 2; 7 j = i + 5; // заменяется на j = 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 data{ char s[3]; float x; int b; } a; 4 байта Для экономии памяти также используется и оператор delete.
6. Сокращение программы Это оптимизация длины самой программы. Многие описанные выше преобразования приводят к сокращению длины. Запроцедуривание – выделение похожих фрагментов в функцию. Критерий времени – первостепеннее.
2. Оптимизация в пределах линейных участков Линейный участок - это последовательность операций с одним входом и одним выходом (первая и последняя операции). Причем все операции выполняются последовательно и внутри участка нет переходов. На линейных участках выполняются обычно 2 оптимизации – свертка и устранение лишних операций.
Внутреннее представление В процессе оптимизации используется внутреннее представление в виде триад. Это конструкция вида операция операнд 1, операнд 2 Причем, если операнд – результат какойто триады, то он задается в виде (i), где i – номер триады-результата. Это называется ссылкой на триаду.
2. 1 Свертка Метод предложен Флойдом. Как было упомянуто в п. 1, свертка – это выполнение операций во время компиляции, если значения операндов известны.
Рассмотрим пример. Исходные выражения i=3 j=i+1 k=j+i j=m l=j+k Исходные триады (1) = 3, i (2) + i, 1 (3) = (2), j (4) + j, i (5) = (4), k (6) = m, j (7) + j, k (8) = (7), l Триады (2) и (4) могут быть вычислены и удалены.
Таблица T Для выполнения свертки формируется таблица T, содержащая пары (a, k), где a – простая переменная, а k – ее текущее значение. Каждая свертываемая триада заменяется новой вида (C k, пусто), означающей, что в этом месте не требуется генерации команд, а k – значение (результат) триады.
Алгоритм свертки Алгоритм последовательно просматривает каждую триаду линейного участка и выполняет для каждой триады (если возможно) следующее: 1. Если операнд – переменная из таблицы T, то он заменяется на соответствующее значение k (из строки (a, k)); 2. Если операнд – ссылка на триаду типа (C k, ), то он заменяется тоже на k;
3. Если все операнды являются константами, то операция может быть свернута. Данная триада вычисляется и вместо нее формируется триада (C k, ), где k – результат триады. 4. Если триада есть операция присвоения a = b, то а) если b – константа, то в таблицу T заносится строка (a, b) (причем старое значение a, если было, исключается); б) если b – не константа, то a со своим значением из таблицы T исключается.
Исходные Действия Новые Таблица T выражения триады алгоритма Триада вычисляется и сворачивается i=3 (1) = 3, i j=i+1 (2) + i, 1 ш1: + 3, 1 ш3 (2) C 4, (3) = (2), j ш2: = 4, j ш4 а) (3) = 4, j (4) + j, i ш1: +3, 4 ш3 (4) C 7, (5) = (4), k ш2: = 7, k ш4 а) (5) = 7, k k=j+i ш. 4 а) (1) = 3, i j=m (6) = m, j ш 4 б (6) = m, j l=j+k (7) + j, k ш1: + j, 7 (7) + j, 7 (8) = (7), l ( i, 3) (j, 4) (k, 7)
2. 2 Исключение лишних операций Определение. i-ая операция линейного участка считается лишней, если существует более ранняя идентичная j-ая операция и никакая переменная, от которой зависит эта i-ая операция, не изменяется третьей операцией, лежащей между i-ой и j-ой операциями.
Пример Для оптимизации также используется представление триадами. x = (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) и dep(i) = dep(j) (следует из 3)). Действительно, если триады i и j – идентичны, то они состоят из одних и тех же операндов. И если бы значение какого-нибудь из них менялось операцией k (j<k<i), то dep(этого операнда) = k (по 2)). Но тогда согласно 3) dep(i) было бы больше dep(j).
Алгоритм исключения лишних операций Алгоритм для каждой i-ой триады делает следующее: 1. Если операнд ссылается на триаду вида (Same j, ), то он заменяется на (j); 2. Вычисляется dep(i) согласно 3) правила; 3. Если существует идентичная j-ая триада, j<i, и dep(i) = dep(j), то i-ая триада – лишняя и она заменяется на (Same j, ). На этом шаге триады просматриваются от i -1, i - 2, … 4. Если i-ая триада является присвоением a = b, то dep(a) = i согласно 2) правила вычисления dep.
Рассмотрим работу алгоритма для операций x = (a + b)*(a + b); a = c; y = a + b; Исходные dep(перем. ) dep Действия Новые a b c x y (триад) алгоритма триады 0 0 0 (1) + a, b 1 (2) + a, b 1 (3) * (1), (2) 2 (4) + a, b 1 (5) * (3), (4) 3 ш2 ш2 (1) + a, b ш3: dep(2)=dep(1) (2)same 1 ш1: (3) *(1), (1) ш2 (3) *(1), (1) ш3: dep(4)=dep(1) (4)same 1 ш1: (5) *(3), (1) ш2 ш2
x = (a + b)*(a + b); a = c; y = a + b; Исходные dep(перем. ) dep Действия Новые триады a b c x y (триад) алгоритма триады 3 0 0 0 (6) = (5), x (7) = c, a 4 (8) + a, b (9) = (8), y 9 ш4 (6) = (5), x ш2 ш4 (7) = c, a 8 7 ш2 1 6 ш2 ш3: но dep(8) dep(1) (8) + a, b 9 ш2 (9) = (8), y ш4 Триады (2), (4) при генерации будут удалены.
Результирующие триады x = (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 – результат операции – рабочая переменная (например, регистр). Причем используются разные рабочие переменные для разных операций.
Алгоритм “чистки вверх” Алгоритм последовательно просматривает операции циклов во внутреннем представлении и 1. Предположим, что встретили операцию о a, b, r 1. Если a и/или b – не инвариантны в этом цикле, то последующие шаги обходятся, иначе на 2. 2. Если в init-участке уже есть подобная операция о a, b, r 2 (может быть с другой рабочей переменной), то все r 1 в цикле заменяем на r 2 и переходим к шагу 4, иначе на 3;
3. Операция добавляется в init-участок; 4. Операция из цикла выбрасывается. r 1 удаляется из var-списка. Исключение r 1 из списка изменяемых переменных позволяет выносить из цикла последующие операции, содержащие r 1 и другой неизменяемый операнд.
Пример. В курсе информатики на первом курсе разбиралась операция АЭМ – адрес элемента массива. Пусть задана матрица, в строке n элементов. Тогда элементу a[i][j] соответствует адрес a + i*n + j (для простоты будем считать, что элемент матрицы занимает 1 элемент памяти).
Пусть задан цикл for ( i = 0; i<n; i++) a[i][j] = a [i][j] + a[j][i]; Разворачиваем цикл во внутреннее представление
a[i][j] = a [i][j] + a[j][i]; АЭМ = a + i*n + j init : i = 0; * j, n, r 3 test: if (i>=n) goto m; loop: * i, n, r 1 ->var-список + r 1, j, r 2 -> var -список * j, n, r 3 -> var -список j, n - инвариантны Чистенько! + r 3, i, r 4 -> var -список + a[r 2], a[r 4], r 5 -> var -список = r 5, , a[r 2] incr: i = i + 1; ш3 -4. Переносим тетраду в goto test; init-участок. m: … r 3 из var-списка удаляем
3. 3 Блок замены сложных операций Рассматривается замена в циклах операции * на +. Алгоритм последовательно анализирует каждую операцию цикла. Встречая операцию * i, k, r 1 или * k, i, r 1 , где переменная k инвариантна в цикле, i – переменная цикла, алгоритм заменяет ее на пустую операцию, а в init-участок и incrучасток добавляет действия в соответствии со следующими соображениями.
При изменении переменной цикла i на шаг b, т. е. i = i + b, в операции * i, k, r 1 будет изменение r 1 = (i + b)*k = i*k + b*k = r 1 + b*k. Так как k – инвариантна в цикле, а шаг b тоже содержит инвариантные величины (см. требования 2, 3 к циклам), то операцию и вычисление b*k можно вынести в init-участок, а вычисление r 1 в incr-участок по операции +.
Алгоритм 1. Если в init-участке уже есть операция * i, k, r 3 , т. е. такая же, но с другой рабочей переменной, то в цикле r 1 заменить на r 3, операцию * i, k, r 1 из цикла удалить. Перейти к шагу 3. 2. Если нет, то в init-участок добавляется операция * i, k, r 1, а из цикла удаляется. 3. Если в init-участке операции вида * b, k, rl нет, то она формируется – это приращение для r 1 (такая операция могла появиться при выполнении чистки цикла). 4. В incr-участок добавляется операция + r 1, rl, r 1
Пример. Из предыдущего параграфа init : i = 0; r 3 = j*n; * i, n, r 1 r 6 = b*n=1*n ; test: if (i>=n) goto m; loop: i – переменная цикла, n – инвариантна в цикле * i, n, r 1 2. . . в init-участок добавляется операция + r 1, j, r 2 * i, k, r 1, + r 3, i, r 4 а из цикла удаляется. + a[r 2], a[r 4], r 5 3. Если в init-участке операции вида = r 5, , a[r 2] * b, k, rl нет, то incr: i = i + 1; + r 1, r 6, r 1; она формируется – это приращение для r 1 4. В incr-участок добавляется операция goto test; + r 1, rl, r 1 m: …
3. 4 Обсуждение некоторых моментов рассмотренных оптимизаций В приведенных выше оптимизациях циклов существенно, чтобы вынесение инвариантных операций работало первым. Например, пусть задано выражение (x + y) * i, где x и y – инвариантны в цикле. Тетрады для него имеют вид (1) + x, y, r 1 (2) * r 1, i, r 2 Замена * в тетраде (2) невозможна, так как r 1 – не инвариантна в цикле. Если же тетраду (1) ранее вынести из цикла, как инвариантную, то r 1 будет исключена из var-списка и можно будет применить алгоритм замены * на +.
Изложенный метод замены сложных операций “не справляется” ( по словам Гриса) с выражениями типа (i + k)*x, где x и k – инвариантны, а i – переменная цикла (1) + i, k, r 1 (2) * r 1, x, r 2 // r 1 – не инвариантна
Выходы из положения: 1. Уметь раскрывать скобки (i + k)*x = i*x + k*x Тогда, init: r 2 = k*x r 1 = i* x r 4 = b*x //приращение для r 1 loop: r 1 = i* x // по 3. 3 r 2 = k*x // по 3. 2 r 3 = r 1+r 2 incr : i = i + b r 1 = r 1 + r 4
(i + k)*x 2. Раскрытие скобок – сложная проблема. Другой подход, не раскрывая скобок. (1) r 1 = i + k; (2) r 2= r 1*x; Во-первых, вынести (1) в init-участок, а в incr-участок - изменение r 1 на тот же шаг b, что и i. Во-вторых, разрешить рабочим переменным участвовать в оптимизации, как переменной цикла i.
(i + k)*x- скобки не раскрываем Таким образом, будем иметь 1 шаг: init : r 1 = i + k; 2 шаг. Разрешив r 1 участвовать в оптимизации как переменной цикла(она меняется на тот же шаг), получим init : r 1 = i + k; r 2 = r 1 * x; r 3 = b *x; // приращение для r 2 loop: test: loop: r 1 = i + k; r 2 = r 1 * x; // r 1 “причислим” к i Лишнее! incr: i = i + b; r 1 = r 1 +b ; incr : i = i + b; r 2 = r 2 + r 3; r 1 = r 1 +b ;
Еще пример, как оптимизации существенно могут упростить цикл. Пусть в цикле вычисляется выражение d = (i * x + y ) * z; x, y, z – инвариантны в цикле init: i = a; r 1 = i*x; r 4 = b*x; // r 1 init: r 2 = r 1 + y; i = a; r 3 = r 2*z; r 5 = r 4*z; // r 3 test: loop: применим 3. 3 r 1= i*x; r 1= i*x r 2 = r 1 + y; к r 1 + y применим шаг 1(см. выше): ; r 3 = r 2*z; к r 3 применим 3. 3, на r 4, при увеличении r 1 придав r 2 = r 1 + y; d = r 3; статус увеличивается на r 4. r 3 = r 2*z; r 2 тоже переменной цикла(выше-шаг 2) d = r 3; incr: i = i + b; r 1 = r 1 + r 4; incr: i = i+b; r 3 r 5; r 2 = r 2 + r 4; r 3 = r 3 + r 5;
Вопросы по гл. 4 1. Что такое идентификация? 2. Что такое определяющее и использующее вхождения лексем? 3. Как реализуется идентификация? 4. Какие ошибки обнаруживаются при идентификации? 5. Что такое атрибутная индукция? Приведите пример.
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: r 1= i*x; r 2 = r 1 + y; r 3 = r 2*z; d = r 3; incr: i = i+b;
trans_5.ppt