Скачать презентацию Простейшие оптимизации программ Ануфриенко Андрей Идрисов Ренат Скачать презентацию Простейшие оптимизации программ Ануфриенко Андрей Идрисов Ренат

5dbe9ef165cfa948d30a418043cf142f.ppt

  • Количество слайдов: 36

Простейшие оптимизации программ Ануфриенко Андрей Идрисов Ренат Простейшие оптимизации программ Ануфриенко Андрей Идрисов Ренат

Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj с ВП Профилировщик Скалярные оптимизации IP/IPO оптимизации HPO Генератор кода Скалярные оптимизации HPO Генератор кода Обьектные файлы Исполняемый файл 10/17/10 Библиотека

Front End Синтаксический анализ (parsing) — это процесс анализа входной последовательности символов, с целью Front End Синтаксический анализ (parsing) — это процесс анализа входной последовательности символов, с целью разбора грамматической структуры, обычно в соответствии с заданной формальной грамматикой. При этом исходный текст преобразуется в структуру данных, обычно — в дерево, которое отражает синтаксическую структуру входной последовательности и хорошо подходит для дальнейшей обработки. Обычно синтаксический анализ делится на два уровня: Лексический анализ — входной поток символов разбивается на линейную последовательность токенов — «слов» языка (напр. целые числа, идентификаторы, строковые константы и т. д. ); Семантический анализ — из токенов выделяются «предложения» языка, согласно грамматическим правилам, и создается дерево разбора На выходе FE мы получаем взаимосвязанные таблицы, которые называются внутренним представлением программы. Обычной практикой является использование общего внутреннего представления для разных языков высокого уровня. 10/17/10

Зависимости (Dependence) Вычисления являются эквивалентными, если на одинаковых данных они вычисляют одинаковые значения для Зависимости (Dependence) Вычисления являются эквивалентными, если на одинаковых данных они вычисляют одинаковые значения для выходных переменных и сохраняется порядок вывода результатов. Это определение позволяет использовать для вычисления различные последовательности инструкций (некоторые из которых могут быть более эффективными, чем другие). Какие особенности утверждений могут привести изменению результата в процессе вычисления?

Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj с ВП Профилировщик Скалярные оптимизации IP/IPO оптимизации HPO Генератор кода Скалярные оптимизации HPO Генератор кода Обьектные файлы Исполняемый файл 10/17/10 Библиотека

Скалярные оптимизации Свертка констант, протяжка копий (Constant folding, constant propagation, copy propagation) Свертка констант Скалярные оптимизации Свертка констант, протяжка копий (Constant folding, constant propagation, copy propagation) Свертка констант - процесс вычисления констант во время компиляции. Протяжка констант – подстановка величин известных констант в выражение int x = 14; int y = 7 - x / 2; => constant propagation => int x = 14; int y = 7 – 14 / 2; Протяжка копий – процесс замены переменных их значениями y = x; z=3+y => copy propagation => z=3+x 10/17/10

Скалярные оптимизации Удаление повторных вычислений (Common subexpression elimination) – поиск идентичных подвыражений и сохранение Скалярные оптимизации Удаление повторных вычислений (Common subexpression elimination) – поиск идентичных подвыражений и сохранение результата вычисления во временной переменной для последующего повторного использования. a = b * c + g; d = b * c * d; => CSE => tmp = b * c; a = tmp + g; d = tmp * d; 10/17/10

Скалярные оптимизации Удаление мертвого кода (Dead code elimination) это удаление кода, который не изменяет Скалярные оптимизации Удаление мертвого кода (Dead code elimination) это удаление кода, который не изменяет выходных данных программы. К мертвому коду относится код, который никогда не выполняется или изменяет только не влияющие на результат переменные. int foo() { int a = 24; int b = 25; /* Присвоение не влияющей на результат переменной */ int c; c = a << 2; return c; b = 24; /* Недостижимый код */ } Мертвый код может появится после многих оптимизаций компилятора, после протяжки констант и копий, после прямой подстановки (inlining) и т. п. 10/17/10

Скалярные оптимизации Удаление излишнего ветвления, протяжка условий Удаляются блоки кода, которые не могут быть Скалярные оптимизации Удаление излишнего ветвления, протяжка условий Удаляются блоки кода, которые не могут быть достижимы из-за цепочки условных ветвлений. if(x>0) { … if(x>0) { a=x; } } else { a=-x; } … } => if(x>0) { … a=x; … } Также может возникнуть из-за скалярных оптимизаций или прямой подстановки. 10/17/10

Анализ потоков данных (Data Flow Analysis) сбор информации о возможном наборе значений переменных вычисляемых Анализ потоков данных (Data Flow Analysis) сбор информации о возможном наборе значений переменных вычисляемых в различных точках программы. Граф потока управления (CFG) используется для определения тех частей программы в которые может быть передано некоторое значение присвоенное переменной. Граф определения/использования (definition-use graph) – это граф, который содержит дуги из каждой точки определения переменной в программе к каждой точке ее использования. 10/17/10

SSA-форма SSA форма не позволяет создавать сложные цепочки зависимостей для переменных. Сила SSA заключается SSA-форма SSA форма не позволяет создавать сложные цепочки зависимостей для переменных. Сила SSA заключается в том, что каждая переменная имеет только одно определение внутри программы. Поэтому любая зависимость очевидна. SSA представление вводит специальные Phi-функции в местах, в местах ветвления или условных операторов (например, if). Это так называемые псевдо-присваивания. При построении необходимо расставить Phi – функции и породить новые уникальные переменные. Новые переменные порождаются путем добавления к имени переменной уникального варианта. SSA-форма может использоваться не только разработчиком при создании программы, но и компилятором в процессе её оптимизации. Любая программа на императивном языке программирования может быть приведена к SSAформе 10/17/10

Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj Исходные файлы FE (C++/C или Fortran) Архитектура компилятора Внутреннее представление Временный файл или Obj с ВП Профилировщик Скалярные оптимизации IP/IPO оптимизации HPO Генератор кода Скалярные оптимизации HPO Генератор кода Обьектные файлы Исполняемый файл 10/17/10 Библиотека

Циклы В большинстве случаев именно циклы являются «горячими местами» программы. Именно поэтому циклам уделяется Циклы В большинстве случаев именно циклы являются «горячими местами» программы. Именно поэтому циклам уделяется много внимания как в архитектуре микропроцессора, так и в компиляторе. Loop Stream Detector – позволяет отказаться от выборки и декодирования инструкций для маленьких циклов. В компиляторе существует множество оптимизаций ориентированных именно на обработку циклов.

Распознание и классификация циклов Такие оптимизации, как правило, могут быть выполнены только для циклов Распознание и классификация циклов Такие оптимизации, как правило, могут быть выполнены только для циклов • с определенным количеством итераций • имеющих последовательно изменяющиеся итерационные переменные, • не имеющих переходов за пределы цикла • не имеющих вызовов неизвестных функций Примеры «хороших» циклов 1. ) for(i=0; i=n) break; while(1);

Примеры «плохих» циклов: 1. ) for(i=0; i<3*i-n; i++) a[i]=i; 2. ) for(i=0; i<n; i++) Примеры «плохих» циклов: 1. ) for(i=0; i<3*i-n; i++) a[i]=i; 2. ) for(i=0; i

Обзор оптимизаций циклических конструкций Значительная часть оптимизаций компилятора связаны с циклами. Вынос инвариантов цикла Обзор оптимизаций циклических конструкций Значительная часть оптимизаций компилятора связаны с циклами. Вынос инвариантов цикла (Loop invariant code motion) – оптимизация, которая находит и выносит за пределы цикла выражения, независящие от индексных переменных цикла. Т. е. это выражение неизменно на каждой итерации. while (j < maximum - 1) { j = j + (4+array[k])*pi+5; } => loop invariant code motion => int maxval = maximum - 1; int calcval = (4+array[k])*pi+5; while (j < maxval) { j = j + calcval; }

Вынос условных переходов (Loop unswitching) – оптимизация, которая выносит условные переходы инвариантные для цикла Вынос условных переходов (Loop unswitching) – оптимизация, которая выносит условные переходы инвариантные для цикла из цикла путем дублирования тела цикла. do i=1, 1000 x[i] = x[i] + y[i]; if (w) then y[i] = 0; end do; => loop unswitching => if (w) then do i=1, 1000 x[i] = x[i] + y[i]; y[i] = 0; end do; else do i=1, 1000 do x[i] = x[i] + y[i]; end do end if;

Тест для оценки эффекта оптимизации loop unswitching: #include <stdio. h> int main() { float Тест для оценки эффекта оптимизации loop unswitching: #include int main() { float x[1000], y[1000]; int i, repeat, p; for(i=0; i<1000; i++) { y[i] = i; x[i] = 1; } for(repeat=0; repeat<3000000; repeat++) { p=repeat%10>5; #ifdef PERF if(p) { for(i=0; i<1000; i++) { x[i]=x[i]+y[i]; y[i]++; } } else { for(i=0; i<1000; i++) { x[i]=x[i]+y[i]; } } #else for(i=0; i<1000; i++) { x[i]=x[i]+y[i]; if(p) { y[i]++; } } #endif } printf("x[123]= %fn", x[123]); } icl –Od test. c –Fetest. exe icl –Od test. c –DPERF – Fetest_opt. exe x[123]= 1799937064960. 000000 CPU time for command: 'test 1. exe' real 11. 703 sec user 11. 672 sec system 0. 000 sec x[123]= 1799937064960. 000000 CPU time for command: 'test_opt. exe' real 10. 563 sec user 10. 328 sec system 0. 000 sec Интеловский компилятор делает эту оптимизацию с опцией –O 3 icl -O 3 test 1. c -debug: minimal Fetest_opt 1. exe x[123]= 1799937064960. 000000 CPU time for command: 'test_opt 1. exe' real 0. 688 sec user 0. 656 sec system 0. 016 sec

Сравнение событий BR_MISSP_EXEC для оригинального и модифицированного теста: Сравнение событий BR_MISSP_EXEC для оригинального и модифицированного теста:

Привязка событий процессора к строкам кода: Привязка событий процессора к строкам кода:

Разбиение, объединение циклов (Loop distribution, loop fusion) – это обратные другу оптимизации. Компилятор должен Разбиение, объединение циклов (Loop distribution, loop fusion) – это обратные другу оптимизации. Компилятор должен иметь инструмент оценки выгодности таких оптимизаций. Разбиение циклов способно улучшить производительность за счет улучшения работы с памятью. Т. е. если цикл работает с большим количеством различных массивов, то может происходить вытеснение из кеша необходимых для последующих операций адресов. Из-за большого количества инвариантов цикла будет происходить вытеснение регистров (register spilling). Есть еще факторы, которые способны улучшить производительность при разбиении циклов. int i, a[100], b[100]; for (i = 0; i < 100; i++) { a[i] = 1; b[i] = 2; } => Loop distribution => for (i = 0; i < 100; i++) { a[i] = 1; } for (i = 0; i < 100; i++) { b[i] = 2; }

Объединение циклов может быть выгодным для небольших циклов за счет улучшения уровня инструкционного параллелизма Объединение циклов может быть выгодным для небольших циклов за счет улучшения уровня инструкционного параллелизма и повторного использования данных. int i, a[100], b[100]; for (i = 0; i < 100; i++) { a[i] = 1; } for (i = 0; i < 100; i++) { b[i] = 2; } => Loop fusion for (i = 0; i < 100; i++) { a[i] = 1; b[i] = 2; } Различные микропроцессоры имеют различные критерии выгодности применения этой оптимизации.

Расщепление цикла (Loop peeling, splitting) – оптимизация, которая пытается упростить цикл «отщеплением» крайних итераций. Расщепление цикла (Loop peeling, splitting) – оптимизация, которая пытается упростить цикл «отщеплением» крайних итераций. p = 10; for (i=0; i<10; ++i) { y[i] = x[i] + x[p]; p = i; } Здесь p=10 только в первой итерации, а в дальнейшем p=i-1 =>Loop peeling => y[0] = x[0] + x[10]; for (i=1; i<10; ++i) { y[i] = x[i] + x[i-1]; }

Развертка цикла (Loop unrolling) – эта техника призвана уменьшить количество условных переходов в цикле. Развертка цикла (Loop unrolling) – эта техника призвана уменьшить количество условных переходов в цикле. Может улучшить инструкционный параллелизм. Несколько итераций цикла объединяются в одну. Есть свои минусы. Может повыситься нагрузка на регистры, увеличивается размер кода, что также может привести к ухудшению производительности. for (int x = 0; x < 100; x++) { delete(x); } => Loop unrolling => for (int x = 0; x < 100; x += 5) { delete(x); delete(x+1); delete(x+2); delete(x+3); delete(x+4); } Полная развертка (complete unrolling) применяется к небольшим циклам и бывает очень эффективна. Как правило в случае вложенных циклов развертываются внутренние циклы.

Перестановка циклов (Loop interchange) – оптимизация при которой меняется порядок итерационных переменных. do i=0, Перестановка циклов (Loop interchange) – оптимизация при которой меняется порядок итерационных переменных. do i=0, 10 do j=0, 20 a[i, j] = i + j => Loop interchange do j=0, 20 do i=0, 10 a[i, j] = i + j ? Для чего выполняется эта оптимизация ?

INTEGER, PARAMETER : : N=100 INTEGER, PARAMETER : : REPEAT=1000 INTEGER : : A(N, INTEGER, PARAMETER : : N=100 INTEGER, PARAMETER : : REPEAT=1000 INTEGER : : A(N, N, N), B(N, N, N) INTEGER : : REP, I, J, K A=1 B=1 DO REP=1, REPEAT DO I=1, N DO J=1, N DO K=1, N A(J, K, I) = A(J, K, I)+B(J, K, I) END DO PRINT *, A(1, 1, 1) END Пример эффективности оптимизации перестановки циклов: ifort -O 3 test. f 90 -o a. out ifort -O 2 test. f 90 -o b. out Сообщение выдаваемое компилятором ( с внутренними ключом) сообщает, что в первом случае компилятор совершил перестановку циклов. … LOOP INTERCHANGE in loops at line: 9 11 12 13 Loopnest permutation ( 1 2 3 4 ) --> ( 2 4 1 3 ) … time. /a. out real 0 m 0. 960 s time. /b. out real 0 m 6. 862 s

Вытеснение данных из кэш: Вытеснение данных из кэш:

INTEGER, PARAMETER : : N=2000 INTEGER : : BF, BN, I, J, K, I INTEGER, PARAMETER : : N=2000 INTEGER : : BF, BN, I, J, K, I 1, J 1, K 1 DOUBLE PRECISION, ALLOCATABLE : : A(: , : ), B(: , : ), C(: , : ) ALLOCATE(A(N, N), B(N, N), C(N, N)) A=1 B=-1 #ifdef PERF BF=8 BN=N/BF DO I 1=1, BF DO J 1=1, BF DO K 1=1, BF DO I=1+BN*(I 1 -1), MIN(BN*I 1, N) DO J=1+BN*(J 1 -1), MIN(BN*J 1, N) DO K=1+BN*(K 1 -1), MIN(BN*K 1, N) C(J, I) = C(J, I) + A(I, K)*B(K, J) END DO END DO #else DO I=1, N DO J=1, N DO K=1, N C(J, I) = C(J, I) + A(I, K)*B(K, J) END DO #endif PRINT *, C(1: 100, 700: 800) END Разбиение цикла на блоки (Loop blocking) ifort loop_blocking. F 90 /fpp –Od -Feoriginal ifort loop_blocking. F 90 /fpp –DPERF –Od –Feblocking Nehalem: Time original. exe ~150 s Time blocking. exe ~105 s

10/17/10 10/17/10

10/17/10 10/17/10

Определение индукционных переменных Выражения, которые являются линейной функцией от счетчиков цикла могут вычисляться прибавлением Определение индукционных переменных Выражения, которые являются линейной функцией от счетчиков цикла могут вычисляться прибавлением константы к значению на предыдущей итерации. for(i=0; i<100; i++) { a[i]=i*8+4; } => temp=4; for(i=0; i<100; i++) { a[i] = temp; temp +=8; } 10/17/10

Другие оптимизации Помимо этих оптимизаций существуют и другие, порой очень нетривиальные: Strength reduction Scalar Другие оптимизации Помимо этих оптимизаций существуют и другие, порой очень нетривиальные: Strength reduction Scalar expansion Loop skewing Loop coalescing Loop collapsing и многие другие. Компилятору в каждом случае необходимо доказать корректность производимой цикловой оптимизации и определить ее выгодность.

Нормализованный цикл Предположим у нас есть цикл: DO I=L, U, S A(I)=… END DO Нормализованный цикл Предположим у нас есть цикл: DO I=L, U, S A(I)=… END DO Для упрощения рассуждений его можно преобразовать к нормализованному циклу: DO J=1, (U-L+S)/S, 1 A(J*S-S+L) = … END DO Так как любой цикл может быть нормализован будем без ограничения общности рассматривать в примерах нормализованные циклы.

/Qopt-report[: n] generate an optimization report to stderr 0 disable optimization report output 1 /Qopt-report[: n] generate an optimization report to stderr 0 disable optimization report output 1 minimum report output 2 medium output (DEFAULT when enabled) 3 maximum report output Пример диагностики: LOOP INTERCHANGE in loops at line: 8 9 Loopnest permutation ( 1 2 ) --> ( 2 1 ) Fusion loop partitions: (loop line numbers) Fused Loops: ( 9 14 ) 10/17/10

Спасибо за внимание! Спасибо за внимание!