Оптимизирующие преобразования программ. Учебное пособие
.pdf
Переменная называется2.4. Преобразованияохватывающейциклов , если при выполнении цикла все её значения, кроме, возможно, первого, образуют арифметическую прогрессию. Индуктивная переменная является частным случаем охватывающей. Охватывающая переменная не всегда является индуктивной. Например, если в первом случае разрешить появление этой переменной в качестве использования раньше, чем в качестве генератора.
Пример 93.
J=N;
FOR (I=1; I<=N; I++); { B[I]=(A[J]+A[I])/2;
J=I;
ЕслиC[])=(A[J]+A[I])/2;отщепить от цикла первую} итерацию, то охватывающая переменная становится индуктивной.
Конец примера.
Для выражения индуктивных переменных через счётчик цикла надо использовать тот факт, что I-й член арифметической прогрессии равен первому члену плюс разность прогрессии, умноженная на (I-1). Рассмотрим получение этих формул для обоих видов индуктивных переменных.
Пусть в теле цикла лишь в одном операторе присваивания эта переменная встречается в качестве генератора и линейно выражается через счётчик цикла. Тогда выполняется преобразование подстановка. Заметим, что всякое использование индуктивной переменной, которое встречается ниже по тексту генератора, заменяется просто правой частью этого оператора присваивания.
Если индуктивная переменная рекуррентно перевычисляется, то её вхождения могут встречаться до генератора и после, и такие вхождения будут по-разному выражаться через счётчик цикла (они заменяются на (I-1)-й и I-й члены арифметической прогрессии соответственно).
91
2. Преобразования программ
Пример 94.
K=3;
J=N;
FOR (I=1; I<=N; I++); { B[I]=(A[J]+A[K])/2;
K=K+1;
ВJ=I;этом}цикле переменная K является индуктивной, а переменная J – охватывающая. Этот цикл эквивалентен следующему:
K=3;
J=N;
B[1]=(A[J]+A[K])/2;
K=K+1;
J=1;
FOR (I=2; I<=N; I++); { B[I]=(A[J]+A[K])/2;
K=K+1;
КонецJ=I; примера} .
Преобразование циклов, содержащих охватывающие переменные, состоит в следующем:
1. Отделяем от цикла первую итерацию. Для остальных итераций охватывающая переменная принимает значения, образующие арифметическую прогрессию.
2. В оставшейся части цикла заменяем охватывающую переменную выражениями, содержащими счётчики цикла: при этом необходимо учитывать, что вхождения охватывающей переменной, которые встречаются до генератора и после, имеют разные значения.
Рассмотренный выше цикл с охватывающей переменной может быть преобразован к следующему фрагменту программы:
J=N;
IF (N>=1); {
B[1]=(A[J]+A[1])/2;
92
2.4. Преобразования циклов
C[1]=(A[1]+A[1])/2;
FOR (I=1; I<=N; I++);{
B(I)=(A(I-1)+A(I))/2
C(I)=(A(I)+A(I))/2;} }
КромеJ=N; индуктивных и охватывающих переменных, в теле цикла могут встречаться и другие переменные, меняющие своё значение в процессе выполнения цикла.
Следующая программа реализует алгоритм параллельнойПример прогонки95. . Переменная L меняет свои значения в процессе выполнения внешнего цикла. Она не является индуктивной или охватывающей, поскольку значения этой переменной образуют геометрическую прогрессию.
FOR (K=1; K<=log(N);K++); {
N=2**M;
L=2**(K-1);
FOR (J=L+1; J<=N; J++); {
H[J]= H[J]-A[J]*H[J-L]; КонецA[J]=A[J]*A[Jпримера. -L];} }
Пример 96.
FOR (I=1; I<=N; I++); { K=2*K;
L=2*L+I;
KK=K+1;
J=L-K;
ВC[I]=A[J]+B[KK];этом примере переменные} K, L, KK, J по-разному меняют свои значения в процессе выполнения цикла, но ни одна из них не является индуктивной или охватывающей переменной.
Конец примера.
93
2. Преобразования программ
Дополнительные примеры
ременнуюПример 97: . Рассматриваемый цикл содержит охватывающую пе-
FOR (I=1; I<=N; I++); { Y[I] = W[I2+3];
I2=2*I-1;
РавносиленX[I]=Y[I]+Z[I2];следующему: }
Y[1] = W[I2+3]; I2=1; X[1]=Y[1]+Z[1];
FOR (I=1; I<=N; I++); { Y[I] = W((2*(I-1)-1)+3);
I2=2*I-1; КонецX[I]=Y[I]+Z(2*Iпримера. -1); }
УпражненияИзбавитьсянаоттемуиндуктивныхивныхохватывающихпеременныхпеременных:
Пример98.
J=N;
For (I=1; I<N; I++) { B[I]=(A[J]+A[I])/2; J=I; C[I]=(A[J]+A[I])/2;
}
менныхПример: 99. Избавиться от индуктивных и охватывающих пере-
For (K=1; K<N; K++) { %%N=2**M L=2**(K-1);
For (J=L+1; J<N; J++) { H[J]= H[J]-A[J]*H[J-L]; A[J]=A[J]*A[J-L];
} }
94
менныхПример: 100. Избавиться2.5. Преобразованияот индуктивныхнеканонических циклови охватывающих пере-
For (I=1; I<N; I++) { K=2*K;
L=2*L+I;
KK=K+1;
J=L-K; C[I]=A[J]+B[KK];
}
менныхПример: 101. Избавиться от индуктивных и охватывающих пере-
For (I=1; I<N; I++) {
Y[I] = W[I2+3] ; I2=2*I-1 ; X[I]=Y[I]+Z[I2] ;
}
менныхПример: 102. Избавиться от индуктивных и охватывающих пере-
J = 2;
For (I=1; I<N; I++) {
K = (J-I)*8+I ; J =I;
X[K] = Y[I] ;
}
2.5. Преобразования неканонических циклов
Обычно преобразования циклов описываются для канонических циклов (циклов стандартного вида). В компиляторах и распараллеливающих системах перед применением преобразования цикл сначала приводится к каноническому виду. Это упрощает раз-
95
работку автоматически применяемых2. Преобразования программпреобразований, поскольку не требуется, например, рассматривать отдельно случаи с положительным и отрицательным шагом цикла. Но иногда возникает потребность в преобразовании циклов неканонического вида. Приведём такие примеры преобразований.
Программа |
2.5.1. Сдвиги границ циклов |
|
|
|
|
for (int i = L; i < R; i++) { |
||
} |
LB(i) |
|
равносильна (всегда, если допустимы величины L+C и R+C) следующей:
for (int i = L+C; i < R+C; i++) {
LB(i-C)
Для }доказательства достаточно выполнить раскрутку обоих циклов.
2.5.2. Расщепление с перестановкой
for (int i = L; i < R; i++) {
РасщеплениеLB(i) с перестановкой состоит в замене исходного цикла следующей последовательностью операторов цикла:
for (int i = M+1; i < R; i++) { LB(i)
for (int i = L; i < M; i++) {
РасщеплениеLB(i) с перестановкой является эквивалентным преобразованием, если в исходном цикле нет дуг циклически порождённой зависимости.
96
2.6.Преобразования функций
2.6.Преобразования функций
Инлайнинг – подстановка2.6.1. Инлайнингвместо вызова функции тела этой функции. Если тело функции FunctionBody(x,y,z) большое, то преобразование инлайнинг может давать ускорение.
Рассмотрим на примере причины ускорения при инлайнинге. Пусть имеется функция F(x,y,z) с входными параметрами x,y,z.
Эта функция где-то описана в тексте программы, и описание начинается с операции с меткой 99.
Тогда вызов функции F(1,2,3) с параметрами 1, 2, 3 преобразуется в ассемблере в код вида
x = 1;. y = 2; z = 3; Goto 99;
88: NextStatement
…
99: FunctionBody(x,y,z);
…Goto 77;
Таким образом, если вместо вызова функции подставить тело функции, то код уменьшится на 2 оператора перехода.
Если тело функции FunctionBody содержит много операций, то уменьшение количества операций на 2 будет несущественно. Более того, если одна функция вызывается много раз, то выполнение инлайнинга может существенно увеличить код и вызвать замедление, поскольку движение команд по микросхеме процессора отнимает время.
Предположим2.6.2. Уменьшение, что функцияколичестваF(x,y,z)параметровнесколькофункциираз вызывает-
ся с каким-то фиксированным параметром, например z=4: F(x,y,4). Тогда можно объявить новую функцию F2(x,y), у которой тело
97
представляет собой FunctionBody(x,y,4),2. Преобразования программгде FunctionBody(x,y,z) –
тело функции F(x,y,z) с параметром z = 4. Тогда вызов функции F2(x,y) имеет вид
x = 1;. y = 2; Goto 99;
88: NextStatement
…
99: FunctionBody(x,y,4); Goto 77;
…
Здесь на один оператор присваивания меньше, чем при вызове функции F(x,y,z).
Более того, в коде FunctionBody(x,y,4) могут быть выполнены упрощения, которые приведут к дополнительным уменьшениям количества операций.
Пусть F(x,y,z) = (x - 5)^2 + (y - 6)^2 + (z - 7)^2. Тогда F2(x,y)Пример= (x103- 5)^2. + (y - 6)^2 + (4 - 7)^2 = (x - 5)^2 + (y - 6)^2 + 9.
В итоге при каждом вызове экономится не только присваивание z = 4, но и вычисление выражения (z - 7)^2.
98
3. РАСПАРАЛЛЕЛИВАНИЕ
3.1. Параллельные процессы
Рассмотрим процессы: 1) землекоп Петя копает траншею; 2)программист Вася пишет программу сортировки на Python; 3) возобновил активность вулкан Этна; 4) вокруг Сатурна вращается спутник.
Эти процессы можно считать независимыми, если не принимать во внимание, что все они являются следствием возникновения Солнечной системы. Такие процессы будем считать параллельно выполняемыми абсолютно или свободно. Все остальные параллельные процессы получаются из свободного параллельного процесса добавлением ограничений. Ограничения бывают по ресурсам (например, на трёх землекопов две лопаты) или это при- чинно-следственные связи (пока Земля не повернулась должным образом вокруг своей оси – день не наступил).
С параллельными вычислительными процессами ситуация аналогичная: вычислить надо 3 функции, а вычислительных ядер 2; пока не вычислим x = 3+2, не можем начать вычисление экспоненты 2^x.
3.2. Параллельные вычислительные архитектуры
Придумано и используется много разных параллельных вычислительных систем. Будем рассматривать два основных (наиболее популярных) принципа параллельных вычислений (параллельные вычислительные архитектуры) – SIMD и MIMD.
99
Архитектура SIMD3. Распараллеливание(Single Instruction Multiple Data) пред-
полагаетSIMD.выполнение в один момент нескольких одинаковых операций над различными данными. Примерами компьютеров с такой архитектурой являются ILLIAK-4, Connection Machine2, отечественный суперкомпьютер ПС-2000 [15] и др. Несмотря на жёсткость ограничений, на эту архитектуру эффективно отображается достаточно представительный класс алгоритмов, имеющих регулярную структуру: задачи линейной алгебры с заполненными матрицами, сеточные методы решения уравнений математической физики в прямоугольных областях, быстрое преобразование Фурье и некоторые др. [15]. Вычислительные архитектуры SIMD с общей памятью – это векторные вычислители (например, популярный в своё время векторный суперкомпьютер Cray1). У каждого процессорного ядра современных массовых многоядерных процессоров есть SIMD-ускоритель (векторизатор). Графические карты тоже относятся к архитектуре SIMD.
Архитектура MIMD (Many Instructions Multiple Data) – «многоMIMDкоманд. – много данных». К этому классу параллельных компьютеров можно отнести многоядерные процессоры, кластерные системы и др. Каждый процессор или процессорное ядро такой архитектуры может работать по своей индивидуальной программе. При этом возникают проблемы с синхронизацией работы процессоров, которая необходима при обращениях к общим данным.
Одна из важных характеристик вычислительной архитектуры – общая (shared) или распределённая (distributed) память. Общая память допускает входные зависимости. Входная зависимость означает, что разные итерации цикла используют одни и те же данные. При распределённой памяти входная зависимость мешает, но может быть устранена предварительной рассылкой данных. В данной работе будем рассматривать только общую память.
Вычислительные системы как с общей, так и с распределённой памятью могут быть и SIMD, и MIMD.
Архитектуры SIMD и MIMD – это наиболее часто встречающиеся в параллельных вычислительных системах широкого назначения. Кроме этих архитектур есть ещё конвейерные, с длинным
100
