Потенциальное совмещение выполнения команд называется параллелизмом на уровне команд.
Самым простым и общим способом увеличения степени параллелизма, доступного на уровне команд, является использование параллелизма между итерациями цикла. Этот тип параллелизма часто называется параллелизмом уровня цикла.
Ниже приведен простой пример цикла, выполняющего сложение двух 1000-элементных векторов, который является полностью параллельным:
for (i = 1; i
x[i] = x[i] + y[i].
Каждая итерация цикла может перекрываться с любой другой итерацией, хотя внутри каждой итерации цикла практическая возможность перекрытия небольшая.
Имеется несколько методов для превращения такого параллелизма уровня цикла в параллелизм уровня команд. Эти методы основаны главным образом на разворачивании цикла либо статически, с использованием компилятора, либо динамически с помощью аппаратуры.
Зависимости в командах и именах программ
Чтобы точно определить, что мы понимаем под параллелизмом уровня цикла и параллелизмом уровня команд, мы должны определить, что такое параллельные команды и параллельные циклы. Начнем с объяснения того, что такое пара параллельных команд.
Две команды являются параллельными, если они могут выполняться одновременно.
Поэтому, если между двумя командами существует взаимозависимость, они не являются параллельными. Имеется три типа зависимостей: зависимости по данным, зависимости по именам и зависимости по управлению.
Команда j зависит по данным от команды i, если имеет место любое из следующих условий:
• команда i вырабатывает результат, который использует команда j;
• команда j является зависимой по данным от команды k, а команда k является зависимой по данным от команды i.
Второе условие просто означает, что одна команда зависит от другой, если между этими двумя командами имеется цепочка зависимостей первого типа. Эта цепочка зависимостей может быть длиною во всю программу.
Если две команды являются зависимыми по данным, они не могут выполняться одновременно или полностью совмещенно. Важность зависимостей по данным заключается в том, что именно они устанавливают верхнюю границу степени параллелизма, который, вероятно, может быть использован. Наличие зависимостей по данным означает также, что результаты должны вычисляться в определенном порядке, поскольку более поздняя команда зависит от результата предыдущей.
Данные могут передаваться от команды к команде либо через регистры, либо через ячейки памяти. Когда данные передаются через регистры, обнаружение зависимостей значительно упрощается, поскольку имена регистров зафиксированы в командах (хотя этот процесс становится более сложным, если вмешиваются условные переходы). Зависимости по данным, которые передаются через ячейки памяти, обнаружить значительно сложнее, поскольку два адреса могут относиться к одной и той же ячейке памяти, но внешне выглядят по-разному.
Методы компиляции для обнаружения таких зависимостей являются очень важными при выявлении параллелизма уровня цикла.
Вторым типом зависимостей в программах являются зависимости по именам. Зависимости по именам возникают, когда две команды используют одно и то же имя (либо регистра, либо ячейки памяти), но при отсутствии передачи данных между командами. Имеется два типа зависимости имен между командой i, которая предшествует команде j в программе:
1. Антизависимость между командой i и командой j возникает тогда, когда команда j записывает результат в регистр или ячейку памяти, который(ую) команда i считывает и команда i выполняется первой.
2. Зависимость по выходу возникает тогда, когда команда i и команда j записывают результат в один и тот же регистр или в одну и ту же ячейку памяти. Порядок выполнения этих команд должен сохраняться.
Как антизависимости, так и зависимости по выходу являются зависимостями по именам, в отличие от истинных зависимостей по данным, поскольку в них отсутствует передача данных от одной команды к другой. Это означает, что команды, связанные зависимостью по именам, могут выполняться одновременно или могут быть переупорядочены, если имя (номер регистра или адрес ячейки памяти), используемое в командах, изменяется так, что команды не конфликтуют. Это переименование может быть выполнено более просто для регистровых операндов и называется переименованием регистров (register renaming). Переименование регистров может выполняться либо статически компилятором, либо динамически – аппаратными средствами.
Последним типом зависимостей являются зависимости по управлению. Зависимости по управлению определяют порядок команд по отношению к команде условного перехода так, что команды, не являющиеся командами перехода, выполняются только тогда, когда они должны выполняться. Каждая команда в программе является зависимой по управлению от некоторого набора условных переходов и, в общем случае, эти зависимости по управлению должны сохраняться. Одним из наиболее простых примеров зависимости по управлению является зависимость операторов, находящихся в части “then” оператора условного перехода if. Например, в последовательности кода:
if p1 {
S1;
};
if p2 {
S2;
}
S1 является зависимым по управлению от p1, а S2 зависит по управлению от p2 и не зависит от p1.
Имеются два ограничения, связанные с зависимостями по управлению:
1. Команда, которая зависит по управлению от условного перехода, не может быть в результате перемещения поставлена перед командой условного перехода так, что ее выполнение более не управлялось бы этим условным переходом. Например, мы не можем взять команду из части “then” оператора if и поставить ее перед оператором if.
2. Команда, которая не является зависимой по управлению от команды условного перехода, не может быть поставлена после команды условного перехода так, что ее выполнение станет управляться этим условным переходом. Например, мы не можем взять оператор, стоящий перед оператором if и перенести его в часть “then” условного оператора.
Параллелизм уровня цикла
Параллелизм уровня цикла обычно анализируется на уровне исходного текста программы или близкого к нему, в то время как анализ параллелизма уровня команд главным образом выполняется, когда команды уже сгенерированы компилятором. Анализ на уровне циклов включает определение того, какие зависимости существуют между операндами в цикле в пределах одной итерации цикла. Мы будем рассматривать только зависимости по данным, которые возникают, когда операнд записывается в некоторой точке и считывается в некоторой более поздней точке.
Анализ параллелизма уровня цикла фокусируется на определении того, зависят ли по данным обращения к данным в последующей итерации от значений данных, вырабатываемых в более ранней итерации.
Рассмотрим следующий цикл:
for (i=1; i
A[i+1] = A[i] + C[i]; /* S1*/
B[i+1] = B[i] + A[i+1]; /*S2*/
}
Предположим, что A, B и C представляют собой отдельные, неперекрывающиеся массивы. Какие зависимости по данным имеют место между операторами этого цикла?
Имеются две различные зависимости:
1. S1 использует значение, вычисляемое оператором S1 на более ранней итерации, поскольку итерация i вычисляет A[i+1], которое считывается в итерации i+1. То же самое справедливо для оператора S2 для B[i] и B[i+1].
2. S2 использует значение A[i+1], вычисляемое оператором S1 в той же самой итерации.
Эти две зависимости отличаются друг от друга и имеют различный эффект. Чтобы увидеть, чем они отличаются, предположим, что в каждый момент времени существует только одна из этих зависимостей. Рассмотрим зависимость оператора S1 от более ранней итерации S1. Эта зависимость означает, что между различными итерациями цикла существует зависимость по данным. Более того, поскольку оператор S1 зависит от самого себя, последовательные итерации оператора S1 должны выполняться упорядоченно.
Вторая зависимость (S2 зависит от S1) не передается от итерации к итерации. Таким образом, если бы это была единственная зависимость, несколько итераций цикла могли бы выполняться параллельно, при условии, что каждая пара операторов в итерации поддерживается в заданном порядке.
Имеется третий тип зависимостей по данным, который возникает в циклах, как показано в следующем примере.
Рассмотрим цикл:
for (i=1; i
A[i] = A[i] + B[i]; /* S1 */
B[i+1] = C[i] + D[i]; /* S2 */
}
Оператор S1 использует значение, которое присваивается оператором S2 в предыдущей итерации, так что имеет место зависимость между S2 и S1 между итерациями.
Несмотря на эту зависимость, этот цикл может быть сделан параллельным. Как и в более раннем цикле – эта зависимость не циклическая: ни один из операторов не зависит сам от себя и хотя S1 зависит от S2, S2 не зависит от S1. Цикл является параллельным, если только отсутствует циклическая зависимость.
Хотя в вышеприведенном цикле отсутствуют циклические зависимости, чтобы выявить параллелизм, он должен быть преобразован в другую структуру. Здесь следует сделать два важных замечания:
1. Зависимость от S1 к S2 отсутствует. Если бы она была, то в зависимостях появился бы цикл и цикл не был бы параллельным. Вследствие отсутствия других зависимостей, перестановка двух операторов не будет влиять на выполнение оператора S2.
2. В первой итерации цикла оператор S1 зависит от значения B[1], вычисляемого перед началом цикла.
Эти два замечания позволяют нам заменить выше приведенный цикл следующей последовательностью:
A[1] = A[1] + B[1];
for (i=1; i
B[i+1] = C[i] + D[i];
A[i+1] = A[i+1] + B[i+1];
}
B[101] = C[100] + D[100].
Теперь итерации цикла могут выполняться с перекрытием, при условии, что операторы в каждой итерации выполняются в заданном порядке.
Имеется множество такого рода преобразований, которые реструктурируют цикл для выявления параллелизма.
Статьи к прочтению:
- Организация параллелизма вычислений в современных процессорах
- Организация перехода к прерывающей программе
Организация вычислений в электронных таблицах | Информатика 9 класс #19 | Инфоурок
Похожие статьи:
-
Организация параллелизма вычислений в современных процессорах
В процессорах пятого и шестого поколений — Pentium, Pentium Pro, Pentium MMX, Pentium II – реализованы различные способы конвейеризации и…
-
Организация распределенных вычислений
В эпоху централизованного использования ЭВМ с пакетной обработкой информации пользователи вычислительной техники предпочитали приобретать компьютеры, на…