Межпроцедурные оптимизации
description
Transcript of Межпроцедурные оптимизации
Межпроцедурные оптимизации
Межпроцедурный анализ
Как совместить хороший стиль программирования и требования к быстродействию приложения?
• Модульность.• Читаемость кода и использование подпрограмм для повторяющихся вычислений.• Принцип реализации утилит как «черного ящика».
Модульность исходного кода усложняет задачу по его оптимизации.
Обсуждаемые в предыдущих разделах оптимизации процедурного уровня:• эффективно работают только с локальными переменными;• всякий вызов функции - «черный ящик»;• неизвестны многие свойства переданных в процедуру параметров;• неизвестны свойства глобальных переменных.
Для решения этих проблем необходимо исследование программы в целом.
Некоторые основные проблемы оптимизаций процедурного уровня
1) Скалярные оптимизации. Reaches(b) = U для всех предшественников (defsout(p) U (reaches(p) ∩
¬killed(p)) В случае вызова неизвестной функции в базовом блоке p необходимо все
глобальные переменные, локальные переменные и доступные функции, поместить в killed(p).
Для качественных скалярных оптимизаций необходимы знания о свойствах функций, вызываемых внутри процедуры.
2) Оптимизации циклических конструкций. Для таких оптимизаций необходимо:• корректное определение объектов, которые могут ссылаться на одну
память;• знание свойств функций внутри циклов (не изменяют итерационные
переменные, не содержат выхода из программы и т.д.);• оценка количества итераций цикла.
3) Векторизация. Необходима информация о выравнивании объектов в памяти.
Протяжка константы через неизвестную функцию
Рассмотрим простую программу:test.c:extern void unknown(int *a);int main(){int a,b,c;
a=5;c=a;unknown(&a);if(a==5) printf("a==5\n");b=a;printf("%d %d %d\n",a,b,c);return(1);}
Сохранится ли if утверждение в результирующем коде?
Ассемблер полученный с помощью icl: icс –O2 test.c –S…call _unknown ;9.1
; LOE ebx esi
.B1.9: ; Preds .B1.8
add esp, 8 ;9.1
; LOE ebx esi
.B1.2: ; Preds .B1.9
mov edi, DWORD PTR [a.3.0.1] ;10.4
cmp edi, 5 ;10.7
jne .B1.4 ; Prob 0% ;10.7
; LOE ebx esi edi
…
Вывод: В общем случае, когда о вызываемой функции ничего не известно, константа, присвоенная переменной, не протягивается через функцию, которая может изменить значение этой переменной.
CSE (Удаление общих подвыражений)#include <math.h>#include <stdio.h>extern void unknown();float a,b;
int main() {float c,d;scanf_s("%f",&a);scanf_s("%f",&b);c=0;if(sqrt(a+b)>3) c=a+b;else unknown();d=sqrt(a+b)+c;printf("d=%f\n",d);return 1;} Здесь есть общее подвыражение sqrt(a+b). Будет ли CSE работать с этим
подвыражением?
icl cse.c –S…
.B1.3:: ; Preds .B1.2
movss xmm0, DWORD PTR [a] ;15.9
pxor xmm14, xmm14 ;14.1
addss xmm0, DWORD PTR [b] ;15.11
cvtps2pd xmm1, xmm0 ;15.11
sqrtsd xmm1, xmm1 ;15.4
comisd xmm1, QWORD PTR [_2il0floatpacket.0] ;15.14
jbe .B1.5 ; Prob 22% ;15.14
; LOE rbx rbp rsi rdi r12 r13 r14 r15 xmm0 xmm1 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15
.B1.4:: ; Preds .B1.3
movaps xmm14, xmm0 ;16.3
jmp .B1.7 ; Prob 100% ;16.3
; LOE rbx rbp rsi rdi r12 r13 r14 r15 xmm1 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15
.B1.5:: ; Preds .B1.3
call unknown ;19.3
; LOE rbx rbp rsi rdi r12 r13 r14 r15 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15
.B1.6:: ; Preds .B1.5
movss xmm0, DWORD PTR [a] ;21.8
addss xmm0, DWORD PTR [b] ;21.10
cvtps2pd xmm1, xmm0 ;21.10
sqrtsd xmm1, xmm1 ;21.3
…
Однопроходная и двухпроходная компиляция
Для того, чтобы собрать информацию о свойствах функций, необходим дополнительный проход. Но, поскольку каждая функция, в свою очередь, может вызывать другие функции, а также себя (рекурсия), то необходимо произвести анализ графа вызовов (Call graph).
Граф вызовов представляет взаимоотношение вызовов между процедурами в программе. Каждая вершина представляет процедуру, и каждая грань (f,g) указывает, что процедура f вызывает процедуру g.
Граф может быть статическим, вычисленным на этапе компиляции или динамическим, т.е. отражающим реальные вызовы при выполнении программы. (VTune).
Одной из основных задач межпроцедурного анализа является построения графа вызовов и выяснения свойств функций на основе его анализа (например, глобальный анализ потоков данных требует знания о том, какие данные каждая функция модифицирует).
Граф вызовов может быть полным и неполным. Если при сборе проекта используются библиотеки, свойства функций которых мы не знаем, то граф будет являться неполным, и полноценный анализ не будет осуществлен.
FE (C++/C или Fortran)
Внутреннее представление
Профилировщик
Скалярные оптимизации HPO
Генератор кода
Исходные файлы
Обьектные файлы
Временный файл или Obj с ВП
IP/IPO оптимизации
Скалярные оптимизации
HPO Генератор кода
Исполняемый файлБиблиотека
Архитектура компилятора
Два вида межпроцедурных оптимизаций:• модульная • оптимизация для всей программы
Qip[-] enable(DEFAULT)/disable single-file IP optimization
within files
/Qipo[n] enable multi-file IP optimization between files
/Qipo-c generate a multi-file object file (ipo_out.obj)
/Qipo-S generate a multi-file assembly file (ipo_out.asm)
/Qipo-jobs<n>
specify the number of jobs to be executed simultaneously during the
IPO link phase
Изменится ли что-либо, если мы определим некую процедуру unknown и перекомпилируем с –ipo:
#include <stdio.h>extern float a,b; void unknown() { printf("a=%f b=%f\n",a,b);}icl –Qipo test.c unknown.c –Ob0 –Qipo-S Работают ли протяжка констант и удаление общих
подвыражений?
Анализ совмещений (alias analysis)
• анализ совмещения через параметры;• анализ совмещения указателей для глобальных и
статических указателей (Local point to analysis - LPT). Важная часть механизма определения зависимостей.
В случае с анализом совмещения указателей с каждым указателем связывается множество возможных значений. Два указателя могут ссылаться на одну область памяти, если пересечение множеств их допустимых значений не пусто.
Пример на LPT анализ#include <stdio.h>int p1=1,p2=2;int *a,*b;void init(int **a, int **b) {*a=&p1;*b=&p1; // <= a and b poins to p1 }
int main() {int i,ar[100];init(&a,&b);printf("*a= %d *b=%d\n",*a,*b);
for(i=0;i<100;i++) {ar[i]=i*(*a)*(*a);*b+=1; // *a is changed through *b}printf("ar[50]= %d p2=%d\n",ar[50],p2);} Можно ли осуществлять цикловые оптимизации с циклом for?
Межпроцедурный анализ используется для протяжки атрибутов функций. Например, есть атрибуты no_side_effect, always_return и т.д.
IPA используется для протяжки атрибутов переменных. Например, переменные, помеченные атрибутом «адрес был взят», исключаются из многих оптимизаций. Этот атрибут для глобальных переменных устанавливает IPA.
Продвижение данных (Data promotion). Каждая переменная имеет свою область видимости (scope). IPA позволяет протягивать данные, которые используются только в определенной процедуре, на уровень этой процедуры, что сразу позволяет включить их во многие оптимизации.
Функции, используемые только в одной программе, получают атрибут static.
Удаление неиспользуемых глобальных переменных. Удаление мертвого кода. В данном случае, функция не будет включаться в исполняемый модуль,
если она не входит в граф вызовов или все ее вызовы были подставлены (inline) (вывод – для улучшения размеров кода и времени компиляции используйте атрибут static).
Протяжка информации о выравнивании аргументов. Если актуальные аргументы функции всегда выровнены, то мы можем улучшить векторизацию внутри процедуры.
Межпроцедурные оптимизации
- используются связи, возникающие при вызовах процедур, для того, чтобы оптимизировать одну или несколько процедур или выяснить, как они соотносятся друг с другом.
Протяжка константных аргументов Если при каждом вызове процедуры f(i,j,k) в программе в качестве
аргумента i всегда передается некая константа, то это позволяет присвоить первому формальному аргументу значение этой константы в коде процедуры f().
Протяжка возвращаемых значений. Если процедура всегда возвращает константное значение, то возможно
протянуть это значение наружу. То же самое касается передаваемых в процедуру аргументов. Если перед
выходом из процедуры значение аргумента константа, то ее также можно протянуть наружу.
Пример. Константа протягивается в функцию через аргумент. Константа протягивается в вызывающую функцию.
cat test.c
#include <stdio.h>
extern void known(int variant,int *var);
int main() {
int var;
int ttt;
var=2;
ttt=3;
known(var,&ttt);
printf("ttt=%i\n",ttt);
}
cat known.cvoid known(int var,int *ttt) { if(var>0) (*ttt)++; else (*ttt)--;}
icc –Ob0 test.c known.c -fast -ipo-S…known:# parameter 1: %edi# parameter 2: %rsi..B2.1: # Preds ..B2.0..___tag_value_known.8: #1.30 addl $1, (%rsi) #3.3 ret #6.1 .align 16,0x90…Таким образом, протянув константу удалось избавиться от ветвления.
Подстановка (inlining) Подстановка удаляет накладные расходы, связанные с подготовкой к
вызову функции, удаляет переходы, которые могут быть источниками неэффективной работы памяти, позволяет эффективнее применять скалярные и оптимизации циклов. Недостаток оптимизации – увеличение размера приложения. Как следствие – увеличение времени компиляции и необходимых для компиляции ресурсов.
Эвристики для подстановок пытаются выбрать наиболее выгодные для подстановки функции, чтобы получить максимальный эффект для производительности, не выходя за пределы допустимого увеличения кода.
Атрибут функции inline
inline int exforsys(int x1) { return 5*x1;}
Программист «рекомендует» компилятору сделать подстановку такой функции.
Управление подстановкойСинтаксис:
#pragma inline[recursive]
#pragma forceinline[recursive]
#pragma noinline
Аргумент Recursive
требует чтобы данная директива применялась ко всем вызовам, которые осуществляются данным вызовом.
Директива inline рекомендует инлайнить
noinline требует не инлайнить
forceinline требует инлайнить
Аналоги для языка Фортран
cDEC$ ATTRIBUTES INLINE :: procedure
cDEC$ ATTRIBUTES NOINLINE :: procedure
cDEC$ ATTRIBUTES FORCEINLINE :: procedure
10/17/10
10/17/10
Компиляторные опции подстановки/Ob<n> control inline expansion:
n=0 disable inlining
n=1 inline functions declared with __inline, and perform C++ inlining
n=2 inline any function, at the compiler's discretion
/Qinline-min-size:<n>
set size limit for inlining small routines
/Qinline-min-size-
no size limit for inlining small routines
/Qinline-max-size:<n>
set size limit for inlining large routines
/Qinline-max-size-
no size limit for inlining large routines
/Qinline-max-total-size:<n>
maximum increase in size for inline function expansion
/Qinline-max-total-size-
no size limit for inline function expansion
10/17/10
/Qinline-max-per-routine:<n>
maximum number of inline instances in any function
/Qinline-max-per-routine-
no maximum number of inline instances in any function
/Qinline-max-per-compile:<n>
maximum number of inline instances in the current compilation
/Qinline-max-per-compile-
no maximum number of inline instances in the current compilation
/Qinline-factor:<n>
set inlining upper limits by n percentage
/Qinline-factor-
do not set set inlining upper limits
/Qinline-forceinline
treat inline routines as forceinline
/Qinline-dllimport
allow(DEFAULT)/disallow functions declared __declspec(dllimport) to be inlined
/Qinline-calloc directs the compiler to inline calloc() calls as malloc()/memset()
Клонирование функций
Если в функцию f(x,y,x) передается x=2 в одном случае и x=3 в другом, то возможно заменить вызов функции f на вызовы f2 и f3.
Частичная подстановка Если в функции f содержится вначале функции код,
зависящий только от формальных аргументов, то этот код может быть подставлен в вызывающую функцию и удален из функции f.
Девиртуализация для C++
Вызов функций через указатели дороже простого вызова. C++ - объектно-ориентированный язык, поддерживающий
высокий уровень абстракции. Возможность выполнения методов функции в зависимости
от типа объекта времени выполнения. A => B => C Все наследуемые классы переопределяют virtual int foo()
int process(class A *a) { return(a->foo()); }
#include <stdio.h>class A { virtual int foo() { return 1; }; friend int process(class A *a);};class B: public A { virtual int foo() { return 2; }; friend int process(class A *a);};int process(class A *a) { return(a->foo());};void main() { A* pA = new A; B* pB = new B;
int result1 = process(pA); int result2 = process(pB); printf("%d,%d\n",result1,result2);}
icl -S rta0.ccРассмотрим ассемблер для функции process:
?process@@YAHPAVA@@@Z PROC NEAR ; parameter 1: 4 + esp.B4.1: ; Preds .B4.0 mov ecx, DWORD PTR [4+esp] ;13.5 mov eax, DWORD PTR [ecx] ;14.9 call DWORD PTR [eax] ;14.9 .B4.2: ; Preds .B4.1 ret ;14.9 ALIGN 16 ; LOE; mark_end;?process@@YAHPAVA@@@Z ENDP
Трансформации данных
Перестановка полей структур. Если несколько полей используются в программе
очень интенсивно, то, разместив их рядом, можно снизить количество промахов по памяти.
Расщепление структуры (structure splitting). Редко используемые поля выносятся в специальную
«холодную» секцию.
Без межпроцедурного анализа нельзя доказать возможность таких оптимизаций.
Перестановка полей и разбиение структурыstruct.h :#ifndef PERFtypedef struct { double x; char title[40]; double y; char title2[22]; double z;} VecR;#elsetypedef struct { char title[40]; char title2[22];} ColdFields;typedef struct { double x; double y; double z; ColdFields *cold;} VecR;#endif
struct.c :#include <stdlib.h>
#include <stdio.h>
#include "struct.h"
int main() {
int i, k;
VecR *array;
array=(VecR*)malloc(10000*sizeof(VecR));
#ifdef PERF
for(i=0;i<10000;i++)
array[i].cold=(ColdFields*)malloc(sizeof(ColdFields));
#endif
for (i=0;i<10000;i++){
array[i].x = 1.0; array[i].y = 2.0; array[i].z = 0.0; }
for(k=1;k<10000;k++) {
for (i=k;i<9999;i++){
array[i].x = array[i-1].y+1.0;
array[i].y = array[i+1].x+array[i+1].y;
array[i].z = (array[i-1].y - array[i-1].x)/array[i-1].y; }
}
printf("%f \n",array[100].z);
#ifdef PERF
for(i=0;i<10000;i++)
free(array[i].cold);
#endif
free(array);
}
icc struct.c -fast -o a.outicc struct.c -fast -DPERF -o b.outtime ./a.outreal 0m0.808stime ./b.outreal 0m0.566s
Излишние ссылки на память (Pointer chasing) Доступ к данным через несколько ссылок – одна из самых
распространенных проблем в С++ коде. Если структуры ваших данных не помещаются в подсистеме памяти, то при разыменовании каждой ссылки вы рискуете ожидать данные из памяти.
10/17/10
Class Employers {Personal_info *p;…};;
Class personal_info {Family_info *f;…};
Class family_info {int members;…};
All_members+= employer->p->f->members;
10/17/10
Спасибо за внимание!