Возможные способы решения этой задачи широко обсуждаются в
литературе; один из наиболее полных обзоров алгоритмов
Вычислительная трудоемкость процедуры упорядочивания является
достаточно высокой. Так, для ряда известных простых методов
(
Для более эффективных алгоритмов (
Данное выражение дает также нижнюю оценку необходимого количества операций для упорядочивания набора из n значений; алгоритмы с меньшей трудоемкостью могут быть получены только для частных вариантов задачи.
(p>1) процессоров. Исходный упорядочиваемый набор в
этом случае разделяется между процессорами; в ходе
Оставляя подробный анализ проблемы
При внимательном рассмотрении способов
Пример 9.1.
// Базовая операция "сравнить и переставить"
if ( A[i] > A[j] ) {
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
Целенаправленное применение данной операции позволяет
упорядочить данные; в способах выбора пар значений для сравнения,
собственно, и проявляется различие алгоритмов
Для параллельного обобщения выделенной базовой операции p=n ) и
на каждом из процессоров содержится только по одному значению
исходного набора данных. Тогда сравнение значений ai и aj,
располагаемых соответственно на процессорах Pi и Pj, можно
организовать следующим образом (параллельное обобщение базовой
операции
Pi и Pj
значений (с сохранением на этих процессорах исходных
элементов);Pi и Pj получившиеся одинаковые
пары значений ( ai, aj ); результаты сравнения используются для
разделения данных между процессорами – на одном процессоре
(например, Pi ) остается меньший элемент, другой процессор (т. е. Pj ) запоминает для дальнейшей обработки большее значение
пары$$a'_i =\min(a_i, a_j), \quad a'_j=\max(a_i,a_j)$$
Рассмотренное параллельное обобщение базовой операции p<n, когда количество процессоров меньше числа
упорядочиваемых значений. В данной ситуации каждый процессор
будет содержать уже не единственное значение, а часть (блок
размера n/p ) сортируемого набора данных.
Определим в качестве результата выполнения параллельного
алгоритма Pi меньше значения первого элемента на процессоре Pi+1, где 0<=i<p-1 или равно ему).
Блоки обычно упорядочиваются в самом начале Pi и Pi+1 для совместного упорядочения содержимого
блоков Ai и Ai+1 может быть осуществлено следующим образом:
Pi и Pi+1 ;Ai и Ai+1 на каждом процессоре в один
отсортированный блок двойного размера (при исходной
упорядоченности блоков Ai и Ai+1 процедура их объединения
сводится к быстрой операции слияния упорядоченных наборов
данных);Pi, а другую часть (с большими значениями,
соответственно) – на процессоре Pi+1$$[A_i \cup A_{i+1}]_{\textit{сорт}}=A'_i \cup a'_{i+1}:
\forall a'_i, \in A'_i, \; \forall a'_j \in A'_{i+1} \Rightarrow a'_i \le a'_j .$$
Рассмотренная процедура обычно именуется в литературе операцией "сравнить и разделить" ( compare-split ). Следует
отметить, что сформированные в результате такой процедуры блоки
на процессорах Pi и Pi+1 совпадают по размеру с исходными блоками Ai и Ai+1 и все значения, расположенные на процессоре Pi, не
превышают значений на процессоре Pi+1.
Определенная выше операция "сравнить и разделить" может быть
использована в качестве базовой подзадачи для организации
параллельных вычислений. Как следует из построения, количество
таких подзадач параметрически зависит от числа имеющихся
процессоров, и, таким образом, проблема масштабирования
вычислений для параллельных алгоритмов
Последовательный алгоритм
(a1, a2, ..., an)
алгоритм сначала выполняет n-1 базовых операций
"сравнения-обмена" для последовательных пар элементов
(a1, a2), (a2, a3), ..., (an-1, an).
В результате после первой итерации алгоритма самый большой элемент перемещается ("всплывает") в конец последовательности. Далее последний элемент в преобразованной последовательности может быть исключен из рассмотрения, и описанная выше процедура применяется к оставшейся части последовательности
(a'1, a'2, ..., a'n-1).
Как можно увидеть, последовательность будет отсортирована
после n-1 итерации.
Алгоритм 9.1. Последовательный алгоритм
// Алгоритм 9.1.
// Последовательный алгоритм пузырьковой сортировки
void BubbleSort(double A[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i; j++)
compare_exchange(A[j], A[j + 1]);
}
Алгоритм
(a1, a2), (a3, a4), ..., (an-1,an) (при четном n),
а на четных итерациях обрабатываются элементы
(a2, a3), (a4, a5), ..., (an-2,an-1).
После n -кратного повторения итераций
Алгоритм 9.2. Последовательный алгоритм чет-нечетной перестановки
// Алгоритм 9.2
// Последовательный алгоритм чет-нечетной перестановки
void OddEvenSort(double A[], int n) {
for (int i = 1; i < n; i++) {
if (i % 2 == 1) { // нечетная итерация
for (int j = 0; j < n/2 - 2; j++)
compare_exchange(A[2*j + 1], A[2*j + 2]);
if (n % 2 == 1) // сравнение последней пары при нечетном n
compare_exchange(A[n - 2], A[n - 1]);
}
else // четная итерация
for (int j = 1; j < n/2 - 1; j++)
compare_exchange(A[2*j], A[2*j + 1]);
}
}
Получение параллельного варианта для метода чет-нечетной
перестановки уже не представляет каких-либо затруднений –
сравнения пар значений на итерациях p<n,
когда количество процессоров меньше числа упорядочиваемых
значений, процессоры содержат блоки данных размера n/p и в
качестве базовой подзадачи может быть использована операция
"сравнить и разделить" (см. подраздел 9.2).
Алгоритм 9.3. Параллельный алгоритм чет-нечетной перестановки
// Алгоритм 9.3
// Параллельный алгоритм чет-нечетной перестановки
ParallelOddEvenSort(double A[], int n) {
int id = GetProcId(); // номер процесса
int np = GetProcNum(); // количество процессов
for (int i = 0; i < np; i++ ) {
if (i % 2 == 1) { // нечетная итерация
if (id % 2 == 1) { // нечетный номер процесса
// Cравнение-обмен с процессом — соседом справа
if (id < np - 1) compare_split_min(id + 1);
}
else
// Cравнение-обмен с процессом — соседом слева
if (id > 0) compare_split_max(id - 1);
}
else { // четная итерация
if(id % 2 == 0) { // четный номер процесса
// Cравнение-обмен с процессом — соседом справа
if (id < np - 1) compare_split_min(id + 1);
}
else
// Cравнение-обмен с процессом — соседом слева
compare_split_max(id - 1);
}
}
}
Для пояснения такого параллельного способа n=16, p=4 (т.е. блок
значений на каждом процессоре содержит n/p=4 элемента). В первом
столбце таблицы приводится номер и тип итерации метода,
перечисляются пары процессов, для которых параллельно выполняются
операции "сравнить и разделить". Взаимодействующие пары процессов
выделены в таблице рамкой. Для каждого шага
В общем случае выполнение параллельного метода может быть
прекращено, если в течение каких-либо двух последовательных
итераций
| № и тип итераци | Процессоры | |||
|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |
| Исходные данные | 13 55 59 88 | 29 43 71 85 | 2 18 40 75 | 4 14 22 43 |
| 1 нечет (1, 2), (3, 4) | 13 55 59 88 | 29 43 71 85 | 2 18 40 75 | 4 14 22 43 |
| 13 29 43 55 | 59 71 85 88 | 2 4 14 18 | 22 40 43 75 | |
| 2 чет (2, 3) | 13 29 43 55 | 59 71 85 88 | 2 4 14 18 | 22 40 43 75 |
| 13 29 43 55 | 2 4 14 18 | 59 71 85 88 | 22 40 43 75 | |
| 3 нечет (1, 2), (3, 4) | 13 29 43 55 | 2 4 14 18 | 59 71 85 88 | 22 40 43 75 |
| 2 4 13 14 | 18 29 43 55 | 22 40 43 59 | 71 75 85 88 | |
| 4 чет (2, 3) | 2 4 13 14 | 18 29 43 55 | 22 40 43 59 | 71 75 85 88 |
| 2 4 13 14 | 18 22 29 40 | 43 43 55 59 | 71 75 85 88 | |
Как отмечалось ранее, количество подзадач соответствует числу
имеющихся процессоров, и поэтому необходимости в проведении
масштабирования вычислений не возникает. Исходное распределение
блоков упорядочиваемого набора данных по процессорам может быть
выбрано совершенно произвольным образом. Для эффективного
выполнения рассмотренного параллельного алгоритма
При анализе
Определим первоначально трудоемкость последовательных
вычислений. При рассмотрении данного вопроса алгоритм T1~n2. Однако применение подобной
оценки сложности последовательного алгоритма приведет к искажению
исходного целевого назначения критериев качества параллельных
вычислений – показатели
Определим теперь сложность рассмотренного параллельного
алгоритма n/p ). Предположим, что данная
начальная
Далее, на каждой выполняемой итерации параллельной p, и, как результат,
общее количество операций этой части параллельных вычислений
оказывается равным$$T_p^2 = 2p(n/p)=2n$$
С учетом полученных соотношений показатели
Расширим приведенные выражения – учтем длительность
выполняемых вычислительных операций и оценим трудоемкость
операции передачи блоков между процессорами. При использовании
модели Хокни общее время всех выполняемых в ходе w есть размер элемента упорядочиваемых данных
в байтах.
С учетом трудоемкости коммуникационных действий общее время
выполнения параллельного алгоритма
Эксперименты осуществлялись на вычислительном кластере
Нижегородского университета на базе процессоров Intel Xeon 4
Для оценки длительности $$\tau$$ базовой скалярной операции алгоритма double, размер которого на данной платформе равен 8 байт
(следовательно w=8 ).
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,002210 | 0,643439 | 0,003270 | 0,434862 |
| 20000 | 0,002991 | 0,004428 | 0,675474 | 0,004596 | 0,650783 |
| 30000 | 0,004612 | 0,006745 | 0,683766 | 0,006873 | 0,671032 |
| 40000 | 0,006297 | 0,008033 | 0,783891 | 0,009107 | 0,691446 |
| 50000 | 0,008014 | 0,009770 | 0,820266 | 0,010840 | 0,739299 |
(рис 9.1) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма пузырьковой сортировкиКак можно заметить из приведенных результатов
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.6) приведено
в таблице 9.3 и на
рис. 9.2.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,002003 | 0,002210 | 0,002057 | 0,003270 |
| 20000 | 0,003709 | 0,004428 | 0,003366 | 0,004596 |
| 30000 | 0,005455 | 0,006745 | 0,004694 | 0,006873 |
| 40000 | 0,007227 | 0,008033 | 0,006035 | 0,009107 |
| 50000 | 0,009018 | 0,009770 | 0,007386 | 0,010840 |
(рис 9.2) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
Общая идея
Общая схема метода состоит в следующем. На первом шаге
алгоритма происходит упорядочивание элементов n/2
пар (ai, an/2+i) для 1<=i<=n/2.
Далее, на втором шаге упорядочиваются элементы в n/4 группах
из четырех элементов (ai, an/4+i, an/2+i, a3n/4+i)
для 1<=i<=n/4. На третьем шаге упорядочиваются элементы уже в n/8 группах
из восьми элементов и т.д. На последнем шаге упорядочиваются
элементы сразу во всем массиве (a1, a2, ..., an). На каждом шаге для
упорядочивания элементов в группах применяется метод log2n.
В более полном виде
Алгоритм 9.4. Последовательный
// Алгоритм 9.4
// Последовательный алгоритм сортировки Шелла
void ShellSort(double A[], int n) {
int incr = n / 2;
while (incr > 0) {
for (int i = incr + 1; i < n; i++) {
int j = i - incr;
while (j > 0)
if (A[j] > A[j + incr]) {
swap(A[j], A[j + incr]);
j = j - incr;
}
else j = 0;
}
incr = incr/2;
}
}
Для N -мерного
гиперкуба (т. е. количество процессоров равно p = 2N ). Выполнение N итераций)
осуществляется взаимодействие процессоров, являющихся соседними в
структуре гиперкуба (но эти процессоры могут оказаться далекими
при линейной нумерации; для установления соответствия двух систем
нумерации процессоров может быть использован, как и ранее, код
Грея – см. лекцию 3). Формирование пар процессоров,
взаимодействующих между собой при выполнении операции "сравнить и
разделить", может быть обеспечено при помощи следующего простого
правила – на каждой итерации i, 0<=i<N, парными становятся
процессоры, у которых различие в битовых представлениях их
номеров имеется только в позиции N-i.
Второй этап состоит в реализации обычных итераций
параллельного алгоритма чет-нечетной перестановки. Итерации
данного этапа выполняются до прекращения фактического изменения
сортируемого набора, и, тем самым, общее количество L таких
итераций может быть различным – от 2 до p.
На рис. 9.3 показан пример
(рис 9.3) Пример работы алгоритма Шелла для 4 процессоровС учетом представленного описания параллельного варианта
Для оценки
Как можно заметить, L – при малом
значении величины L новый параллельный способ
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,002959 | 0,480568 | 0,007509 | 0,189373 |
| 20000 | 0,002991 | 0,004557 | 0,656353 | 0,009826 | 0,304396 |
| 30000 | 0,004612 | 0,006118 | 0,753841 | 0,012431 | 0,371008 |
| 40000 | 0,006297 | 0,008461 | 0,744238 | 0,017009 | 0,370216 |
| 50000 | 0,008014 | 0,009920 | 0,807863 | 0,019419 | 0,412689 |
(рис 9.4) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма сортировки ШеллаСравнение времени выполнения эксперимента $$T^*_p$$ и теоретической
оценки Tp из (9.7) приведено в таблице 9.5 и на рис. 9.5.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 1000 | 0,002684 | 0,002959 | 0,002938 | 0,007509 |
| 20000 | 0,004872 | 0,004557 | 0,004729 | 0,009826 |
| 30000 | 0,007100 | 0,006118 | 0,006538 | 0,012431 |
| 40000 | 0,009353 | 0,008461 | 0,008361 | 0,017009 |
| 50000 | 0,011625 | 0,009920 | 0,010193 | 0,019419 |
(рис 9.5) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
При общем рассмотрении алгоритма log2n итераций исходный массив данных
оказывается упорядоченным. Более подробное изложение метода может
быть получено, например, в [, ].
T1~n2 ). При оптимальном выборе ведущих элементов, когда
разделение каждого блока происходит на равные по размеру части,
трудоемкость алгоритма совпадает с быстродействием наиболее
эффективных способов
Общая схема алгоритма
Алгоритм 9.5. Последовательный алгоритм
// Алгоритм 9.5
// Последовательный алгоритм быстрой сортировки
void QuickSort(double A[], int i1, int i2) {
if (i1 < i2) {
double pivot = A[i1];
int is = i1;
for (int i = i1 + 1; i < i2; i++)
if (A[i] Ј pivot) {
is = is + 1;
swap(A[is], A[i]);
}
swap(A[i1], A[is]);
QuickSort(A, i1, is);
QuickSort(A, is + 1, i2);
}
}
Параллельное обобщение алгоритма N -мерного гиперкуба (т.е. p=2N ). Пусть, как
и ранее, исходный набор данных распределен между процессорами
блоками одинакового размера n/p ; результирующее расположение
блоков должно соответствовать нумерации процессоров гиперкуба.
Возможный способ выполнения первой итерации параллельного метода
при таких условиях может состоять в следующем:
N, и
осуществить взаимообмен данными между этими процессорами.В результате выполнения такой итерации N равен 0. Таких процессоров всего p/2, и, таким
образом, исходный N -мерный гиперкуб также оказывается разделенным
на два гиперкуба размерности N-1. К этим подгиперкубам, в свою
очередь, может быть параллельно применена описанная выше
процедура. После N -кратного повторения подобных итераций для
завершения
Для пояснения на рис. 9.6 представлен пример упорядочивания
данных при n=16, p=4 (т.е. блок каждого процессора содержит 4
элемента). На этом рисунке процессоры изображены в виде
прямоугольников, внутри которых показано содержимое
упорядочиваемых блоков данных; значения блоков приводятся в
начале и при завершении каждой итерации
(рис 9.6) Пример упорядочивания данных параллельным методом быстрой сортировки (без результатов локальной сортировки блоков)Как и ранее, в качестве базовой подзадачи для организации
параллельных вычислений может быть выбрана операция "сравнить и
разделить", а количество подзадач совпадает с числом используемых
процессоров. Распределение подзадач по процессорам должно
производиться с учетом возможности эффективного выполнения
алгоритма при представлении
Оценим трудоемкость рассмотренного параллельного метода. Пусть
у нас имеется N -мерный гиперкуб, состоящий из p=2N процессоров,
где p<n.
Определим вначале вычислительную сложность алгоритма log2p итераций n/p операций (будем
предполагать, что на каждой итерации
При завершении вычислений процессор выполняет (n/p)log2(n/p) операций.
Таким образом, общее время вычислений параллельного алгоритма
Рассмотрим теперь сложность выполняемых коммуникационных
операций. Общее количество межпроцессорных обменов для рассылки
ведущего элемента на N -мерном гиперкубе может быть
ограничено оценкой$$\sum_{i=1}^N i = N(N+1)/2 = \log_2 p (log_2 p+1)/2 \sim (\log_2 p)^2 .$$
При используемых предположениях (выбор ведущих элементов
осуществляется наилучшим образом) количество итераций алгоритма
равно log2p, а объем передаваемых данных между процессорами
всегда равен половине блока, т.е. величине (n/p)/2. При таких
условиях коммуникационная сложность параллельного алгоритма w есть размер элемента набора в байтах.
С учетом всех полученных соотношений общая трудоемкость алгоритма оказывается равной$$T_p=[(n/p)\log_2 p+(n/p)\log_2(n/p)]\tau+ (log_2 p)^2(\alpha+w/ \beta) + \log_2 p(\alpha+w(n/2p)/ \beta).$$
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001521 | 0,934911 | 0,003434 | 0,414094 |
| 20000 | 0,002991 | 0,002234 | 1,338854 | 0,004094 | 0,730581 |
| 30000 | 0,004612 | 0,003080 | 1,497403 | 0,005088 | 0,906447 |
| 40000 | 0,006297 | 0,004363 | 1,443273 | 0,005906 | 1,066204 |
| 50000 | 0,008014 | 0,005486 | 1,460809 | 0,006635 | 1,207837 |
Как можно заметить по результатам
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.11)
приведено в таблице 9.7 и на
рис. 9.8.
(рис 9.7) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма быстрой сортировки| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001280 | 0,001521 | 0,001735 | 0,003434 |
| 20000 | 0,002265 | 0,002234 | 0,002321 | 0,004094 |
| 30000 | 0,003289 | 0,003080 | 0,002928 | 0,005088 |
| 40000 | 0,004338 | 0,004363 | 0,003547 | 0,005906 |
| 50000 | 0,005407 | 0,005486 | 0,004175 | 0,006635 |
(рис 9.8) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
В
Все остальные действия в новом рассматриваемом алгоритме
выполняются в соответствии с обычным методом
При анализе (n/p)/2). Кроме того, в силу
упорядоченности блоков может быть усовершенствована процедура
деления – вместо перебора всех элементов блока теперь достаточно
будет выполнить для ведущего элемента бинарный поиск в блоке. С
учетом всех высказанных замечаний трудоемкость обобщенного
алгоритма
Представим возможный вариант параллельной программы
1. Главная функция программы. Реализует логику работы алгоритма, последовательно вызывает необходимые подпрограммы.
// Программа 9.1.
// Обобщенная быстрая сортировка
int ProcRank; // Ранг текущего процесса
int ProcNum; // Количество процессов
int main(int argc, char *argv[]) {
double *pProcData; // Блок данных процесса
int ProcDataSize; // Размер блока данных
MPI_Init(argc, argv);
MPI_Comm_rank(MPI_COMM_WORLD, ProcRank);
MPI_Comm_size(MPI_COMM_WORLD, ProcNum);
// Инициализация данных и их распределение между процессами
ProcessInitialization(pProcData, ProcDataSize);
// Параллельная сортировка
ParallelHyperQuickSort(pProcData, ProcDataSize);
// Завершение вычислений процесса
ProcessTermination(pProcData, ProcDataSize);
MPI_Finalize();
}
Функция ProcessInitialization определяет исходные данные
решаемой задачи (размер сортируемого массива), выделяет память
для хранения данных, осуществляет генерацию сортируемого массива
(например, при помощи датчика случайных чисел) и распределяет его
между процессами.
Функция ProcessTermination осуществляет
необходимый вывод результатов решения задачи и освобождает всю
ранее выделенную память для хранения данных.
Реализация всех перечисленных функций может быть выполнена по аналогии с ранее рассмотренными примерами и предоставляется читателю в качестве самостоятельного упражнения.
2. Функция ParallelHyperQuickSort.
Функция производит параллельную
// Функция для выполнения обощенного алгоритма быстрой сортировки
void ParallelHyperQuickSort ( double *pProcData,
int ProcDataSize) {
MPI_Status status;
int CommProcRank; // Ранг процессора, с которым выполняется
// взаимодействие
double *pData, // Часть блока, остающаяся на процессоре
*pSendData, // Часть блока, передаваемая процессору
// CommProcRank
*pRecvData, // Часть блока, получаемая от процессора
// CommProcRank
*pMergeData; // Блок данных, получаемый после слияния
int DataSize, SendDataSize, RecvDataSize, MergeDataSize;
int HypercubeDim = (int)ceil(log(ProcNum)/log(2));
// размерность гиперкуба
int Mask = ProcNum;
double Pivot;
// Первоначальная сортировка блоков данных на каждом процессоре
LocalDataSort(pProcData, ProcDataSize);
// Итерации обобщенной быстрой сортировки
for (int i = HypercubeDim; i > 0; i-- ) {
// Определение ведущего значения и его рассылка всем процессорам
PivotDistribution(pProcData, ProcDataSize, HypercubeDim,
Mask, i,Pivot);
Mask = Mask >> 1;
// Определение границы разделения блока
int pos = GetProcDataDivisionPos(pProcData, ProcDataSize, Pivot);
// Разделение блока на части
if ( ( (rank Mask) >> (i - 1) ) == 0 ) { // старший бит = 0
pSendData = pProcData[pos + 1];
SendDataSize = ProcDataSize - pos – 1;
if (SendDataSize < 0) SendDataSize = 0;
CommProcRank = ProcRank + Mask
pData = pProcData[0];
DataSize = pos + 1;
}
else { // старший бит = 1
pSendData = pProcData[0];
SendDataSize = pos + 1;
if (SendDataSize > ProcDataSize) SendDataSize = pos;
CommProcRank = ProcRank – Mask
pData = pProcData[pos + 1];
DataSize = ProcDataSize - pos - 1;
if (DataSize < 0) DataSize = 0;
}
// Пересылка размеров частей блоков данных
MPI_Sendrecv(SendDataSize, 1, MPI_INT, CommProcRank, 0,
RecvDataSize, 1, MPI_INT, CommProcRank, 0, MPI_COMM_WORLD, status);
// Пересылка частей блоков данных
pRecvData = new double[RecvDataSize];
MPI_Sendrecv(pSendData, SendDataSize, MPI_DOUBLE,
CommProcRank, 0, pRecvData, RecvDataSize, MPI_DOUBLE,
CommProcRank, 0, MPI_COMM_WORLD, status);
// Слияние частей
MergeDataSize = DataSize + RecvDataSize;
pMergeData = new double[MergeDataSize];
DataMerge(pMergeData, pMergeData, pData, DataSize,
pRecvData, RecvDataSize);
delete [] pProcData;
delete [] pRecvData;
pProcData = pMergeData;
ProcDataSize = MergeDataSize;
}
}
Функция LocalDataSort выполняет
Функция PivotDistribution определяет ведущий
элемент и рассылает его значение всем процессорам.
Функция GetProcDataDivisionPos выполняет
разделение блока данных относительно ведущего элемента. Ее
результатом является целое число, обозначающее позицию элемента
на границе двух блоков.
Функция DataMerge осуществляет слияние частей в
один упорядоченный блок данных.
3. Функция PivotDistribution. Функция
выбирает ведущий элемент и рассылает его все процессорам
гиперкуба. Так как данные на процессорах отсортированы с самого
начала, ведущий элемент выбирается как средний элемент блока
данных.
// Функция выбора и рассылки ведущего элемента
void PivotDistribution (double *pProcData, int ProcDataSize, int Dim,
int Mask, int Iter, double *pPivot) {
MPI_Group WorldGroup;
MPI_Group SubcubeGroup; // Группа процессов — подгиперкуб
MPI_Comm SubcubeComm; // Коммуникатор подгиперкуба
int j = 0;
int GroupNum = ProcNum /(int)pow(2, Dim-Iter);
int *ProcRanks = new int [GroupNum];
// формирование списка рангов процессов для гиперкуба
int StartProc = ProcRank – GroupNum;
if (StartProc < 0) StartProc = 0;
int EndProc = ProcRank + GroupNum;
if (EndProc > ProcNum) EndProc = ProcNum;
for (int proc = StartProc; proc < EndProc; proc++) {
if ((ProcRank Mask)>>(Iter) == (proc Mask)>>(Iter)) {
ProcRanks[j++] = proc;
}
}
// Объединение процессов подгиперкуба в одну группу
MPI_Comm_group(MPI_COMM_WORLD, WorldGroup);
MPI_Group_incl(WorldGroup, GroupNum, ProcRanks, SubcubeGroup);
MPI_Comm_create(MPI_COMM_WORLD, SubcubeGroup, SubcubeComm);
// Поиск и рассылка ведущего элемента всем процессам подгиперкуба
if (ProcRank == ProcRanks[0])
*pPivot = pProcData[ProcDataSize / 2];
MPI_Bcast(pPivot, 1, MPI_DOUBLE, 0, SubcubeComm);
MPI_Group_free(SubcubeGroup);
MPI_Comm_free(SubcubeComm);
delete [] ProcRanks;
}
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001485 | 0,957576 | 0,002898 | 0,490683 |
| 20000 | 0,002991 | 0,002180 | 1,372018 | 0,003770 | 0,793369 |
| 30000 | 0,004612 | 0,003077 | 1,498863 | 0,004451 | 1,036172 |
| 40000 | 0,006297 | 0,003859 | 1,631770 | 0,004721 | 1,333828 |
| 50000 | 0,008014 | 0,005041 | 1,589764 | 0,005242 | 1,528806 |
(рис 9.9) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма обобщенной быстрой сортировкиСравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.12)
приведено в таблице 9.9 и на
рис. 9.10.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001281 | 0,001485 | 0,001735 | 0,002898 |
| 20000 | 0,002265 | 0,002180 | 0,002322 | 0,003770 |
| 30000 | 0,003289 | 0,003077 | 0,002928 | 0,004451 |
| 40000 | 0,004338 | 0,003859 | 0,003547 | 0,004721 |
| 50000 | 0,005407 | 0,005041 | 0,004176 | 0,005242 |
(рис 9.10) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных
Алгоритм
0, m, 2m, ...,(p-1)m, где m=n/p2 ;p
частей с использованием полученного набора ведущих значений;j,
0<=j<p, каждого блока пересылается процессору с
номером j ;По завершении четвертого этапа исходный набор данных становится отсортированным.
На рис. 9.11 приведен
пример
(рис 9.11) Пример работы алгоритма сортировки с использованием регулярного набора образцов
Оценим трудоемкость рассмотренного параллельного метода.
Пусть, как и ранее, n есть количество сортируемых
данных, p, p<n, обозначает число используемых
процессоров и, соответственно, n/p есть размер
блоков данных на процессорах.
В течение первого этапа алгоритма каждый процессор сортирует
свой блок данных с помощью
На втором этапе алгоритма один из процессоров собирает наборы
из p элементов со всех остальных процессоров, выполняет слияние
всех полученных данных (общее количество элементов составляет p2 ), формирует набор из p-1 ведущих элементов и рассылает
полученный набор всем остальным процессорам. С учетом всех
перечисленных действий общая длительность второго этапа
составляет$$T_p^2=[\alpha\log_2p+wp(p-1)/ \beta]+
[p^2\log_2p\tau]+[p\tau]+
[\log_2p(\alpha+wp/ \beta)]$$
(в приведенном соотношении выделенные подвыражения соответствуют
четырем перечисленным действиям алгоритма); здесь, как и ранее, $$\alpha$$ – латентность, $$\beta$$ – пропускная способность сети
передачи данных, а w есть размер элемента
упорядочиваемых данных в байтах.
В ходе выполнения третьего этапа алгоритма каждый процессор
разделяет свои элементы относительно ведущих элементов на p
частей (общее количество операций для этого может быть ограничено
величиной n/p ). Далее все процессоры выполняют рассылку
сформированных частей блоков между собой – оценка трудоемкости
такой коммуникационной операции рассмотрена в лекции 3 при
представлении топологии вычислительной сети в виде гиперкуба. Как
было показано, выполнение такой операции может быть осуществлено
за log2p шагов, на каждом из которых каждый процессор передает и
получает сообщение из (n/p)/2 элементов. Как результат, общая
трудоемкость третьего этапа алгоритма может быть оценена как$$T_p^3=(n/p)\tau+\log_2p(\alpha+w(n/2p)/ \beta).$$
На четвертом этапе алгоритма каждый процессор выполняет
слияние p отсортированных частей в один объединенный блок. Оценка
трудоемкости такой операции уже проводилась при рассмотрении
второго этапа, и, тем самым, длительность выполнения процедуры
слияния составляет$$T_p^4=(n/p)\log_2p\tau.$$
С учетом всех полученных соотношений общее время выполнения
алгоритма
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001513 | 0,939855 | 0,001166 | 1,219554 |
| 20000 | 0,002991 | 0,002307 | 1,396489 | 0,002081 | 1,437290 |
| 30000 | 0,004612 | 0,003168 | 1,455808 | 0,003099 | 1,488222 |
| 40000 | 0,006297 | 0,004542 | 1,386394 | 0,003819 | 1,648861 |
| 50000 | 0,008014 | 0,005503 | 1,456297 | 0,004370 | 1,833867 |
(рис 9.12) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма сортировки с использованием регулярного набора образцов| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001533 | 0,001513 | 0,001762 | 0,001166 |
| 20000 | 0,002569 | 0,002307 | 0,002375 | 0,002081 |
| 30000 | 0,003645 | 0,003168 | 0,003007 | 0,003099 |
| 40000 | 0,004747 | 0,004542 | 0,003652 | 0,003819 |
| 50000 | 0,005867 | 0,005503 | 0,004307 | 0,004370 |
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.17)
приведено в таблице 9.11 и на
рис. 9.13.
(рис 9.13) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных
В лекции рассматривается часто встречающаяся в приложениях задача упорядочения данных, для решения которой в рамках данного
учебного материала выбраны широко известные алгоритмы
Алгоритм
Для алгоритма Шелла (подраздел 9.4) рассматривается схема
распараллеливания при представлении топологии сети в виде
гиперкуба. При таком представлении топологии оказывается
возможной организация взаимодействия процессоров, расположенных
далеко друг от друга при линейной нумерации. Как правило, такая
организация вычислений позволяет уменьшить количество выполняемых
итераций алгоритма
Для алгоритма
При применении алгоритма
Возможные способы решения задачи упорядочения данных широко обсуждаются в литературе; один из наиболее полных обзоров ], среди последних изданий может быть рекомендована работа [].
Параллельные варианты алгоритма
Схемы распараллеливания
Полезной при рассмотрении вопросов параллельных вычислений для
Возможные способы решения этой задачи широко обсуждаются в
литературе; один из наиболее полных обзоров алгоритмов
Вычислительная трудоемкость процедуры упорядочивания является
достаточно высокой. Так, для ряда известных простых методов
(
Для более эффективных алгоритмов (
Данное выражение дает также нижнюю оценку необходимого количества операций для упорядочивания набора из n значений; алгоритмы с меньшей трудоемкостью могут быть получены только для частных вариантов задачи.
(p>1) процессоров. Исходный упорядочиваемый набор в
этом случае разделяется между процессорами; в ходе
Оставляя подробный анализ проблемы
При внимательном рассмотрении способов
Пример 9.1.
// Базовая операция "сравнить и переставить"
if ( A[i] > A[j] ) {
temp = A[i];
A[i] = A[j];
A[j] = temp;
}
Целенаправленное применение данной операции позволяет
упорядочить данные; в способах выбора пар значений для сравнения,
собственно, и проявляется различие алгоритмов
Для параллельного обобщения выделенной базовой операции p=n ) и
на каждом из процессоров содержится только по одному значению
исходного набора данных. Тогда сравнение значений ai и aj,
располагаемых соответственно на процессорах Pi и Pj, можно
организовать следующим образом (параллельное обобщение базовой
операции
Pi и Pj
значений (с сохранением на этих процессорах исходных
элементов);Pi и Pj получившиеся одинаковые
пары значений ( ai, aj ); результаты сравнения используются для
разделения данных между процессорами – на одном процессоре
(например, Pi ) остается меньший элемент, другой процессор (т. е. Pj ) запоминает для дальнейшей обработки большее значение
пары$$a'_i =\min(a_i, a_j), \quad a'_j=\max(a_i,a_j)$$
Рассмотренное параллельное обобщение базовой операции p<n, когда количество процессоров меньше числа
упорядочиваемых значений. В данной ситуации каждый процессор
будет содержать уже не единственное значение, а часть (блок
размера n/p ) сортируемого набора данных.
Определим в качестве результата выполнения параллельного
алгоритма Pi меньше значения первого элемента на процессоре Pi+1, где 0<=i<p-1 или равно ему).
Блоки обычно упорядочиваются в самом начале Pi и Pi+1 для совместного упорядочения содержимого
блоков Ai и Ai+1 может быть осуществлено следующим образом:
Pi и Pi+1 ;Ai и Ai+1 на каждом процессоре в один
отсортированный блок двойного размера (при исходной
упорядоченности блоков Ai и Ai+1 процедура их объединения
сводится к быстрой операции слияния упорядоченных наборов
данных);Pi, а другую часть (с большими значениями,
соответственно) – на процессоре Pi+1$$[A_i \cup A_{i+1}]_{\textit{сорт}}=A'_i \cup a'_{i+1}:
\forall a'_i, \in A'_i, \; \forall a'_j \in A'_{i+1} \Rightarrow a'_i \le a'_j .$$
Рассмотренная процедура обычно именуется в литературе операцией "сравнить и разделить" ( compare-split ). Следует
отметить, что сформированные в результате такой процедуры блоки
на процессорах Pi и Pi+1 совпадают по размеру с исходными блоками Ai и Ai+1 и все значения, расположенные на процессоре Pi, не
превышают значений на процессоре Pi+1.
Определенная выше операция "сравнить и разделить" может быть
использована в качестве базовой подзадачи для организации
параллельных вычислений. Как следует из построения, количество
таких подзадач параметрически зависит от числа имеющихся
процессоров, и, таким образом, проблема масштабирования
вычислений для параллельных алгоритмов
Последовательный алгоритм
(a1, a2, ..., an)
алгоритм сначала выполняет n-1 базовых операций
"сравнения-обмена" для последовательных пар элементов
(a1, a2), (a2, a3), ..., (an-1, an).
В результате после первой итерации алгоритма самый большой элемент перемещается ("всплывает") в конец последовательности. Далее последний элемент в преобразованной последовательности может быть исключен из рассмотрения, и описанная выше процедура применяется к оставшейся части последовательности
(a'1, a'2, ..., a'n-1).
Как можно увидеть, последовательность будет отсортирована
после n-1 итерации.
Алгоритм 9.1. Последовательный алгоритм
// Алгоритм 9.1.
// Последовательный алгоритм пузырьковой сортировки
void BubbleSort(double A[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i; j++)
compare_exchange(A[j], A[j + 1]);
}
Алгоритм
(a1, a2), (a3, a4), ..., (an-1,an) (при четном n),
а на четных итерациях обрабатываются элементы
(a2, a3), (a4, a5), ..., (an-2,an-1).
После n -кратного повторения итераций
Алгоритм 9.2. Последовательный алгоритм чет-нечетной перестановки
// Алгоритм 9.2
// Последовательный алгоритм чет-нечетной перестановки
void OddEvenSort(double A[], int n) {
for (int i = 1; i < n; i++) {
if (i % 2 == 1) { // нечетная итерация
for (int j = 0; j < n/2 - 2; j++)
compare_exchange(A[2*j + 1], A[2*j + 2]);
if (n % 2 == 1) // сравнение последней пары при нечетном n
compare_exchange(A[n - 2], A[n - 1]);
}
else // четная итерация
for (int j = 1; j < n/2 - 1; j++)
compare_exchange(A[2*j], A[2*j + 1]);
}
}
Получение параллельного варианта для метода чет-нечетной
перестановки уже не представляет каких-либо затруднений –
сравнения пар значений на итерациях p<n,
когда количество процессоров меньше числа упорядочиваемых
значений, процессоры содержат блоки данных размера n/p и в
качестве базовой подзадачи может быть использована операция
"сравнить и разделить" (см. подраздел 9.2).
Алгоритм 9.3. Параллельный алгоритм чет-нечетной перестановки
// Алгоритм 9.3
// Параллельный алгоритм чет-нечетной перестановки
ParallelOddEvenSort(double A[], int n) {
int id = GetProcId(); // номер процесса
int np = GetProcNum(); // количество процессов
for (int i = 0; i < np; i++ ) {
if (i % 2 == 1) { // нечетная итерация
if (id % 2 == 1) { // нечетный номер процесса
// Cравнение-обмен с процессом — соседом справа
if (id < np - 1) compare_split_min(id + 1);
}
else
// Cравнение-обмен с процессом — соседом слева
if (id > 0) compare_split_max(id - 1);
}
else { // четная итерация
if(id % 2 == 0) { // четный номер процесса
// Cравнение-обмен с процессом — соседом справа
if (id < np - 1) compare_split_min(id + 1);
}
else
// Cравнение-обмен с процессом — соседом слева
compare_split_max(id - 1);
}
}
}
Для пояснения такого параллельного способа n=16, p=4 (т.е. блок
значений на каждом процессоре содержит n/p=4 элемента). В первом
столбце таблицы приводится номер и тип итерации метода,
перечисляются пары процессов, для которых параллельно выполняются
операции "сравнить и разделить". Взаимодействующие пары процессов
выделены в таблице рамкой. Для каждого шага
В общем случае выполнение параллельного метода может быть
прекращено, если в течение каких-либо двух последовательных
итераций
| № и тип итераци | Процессоры | |||
|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |
| Исходные данные | 13 55 59 88 | 29 43 71 85 | 2 18 40 75 | 4 14 22 43 |
| 1 нечет (1, 2), (3, 4) | 13 55 59 88 | 29 43 71 85 | 2 18 40 75 | 4 14 22 43 |
| 13 29 43 55 | 59 71 85 88 | 2 4 14 18 | 22 40 43 75 | |
| 2 чет (2, 3) | 13 29 43 55 | 59 71 85 88 | 2 4 14 18 | 22 40 43 75 |
| 13 29 43 55 | 2 4 14 18 | 59 71 85 88 | 22 40 43 75 | |
| 3 нечет (1, 2), (3, 4) | 13 29 43 55 | 2 4 14 18 | 59 71 85 88 | 22 40 43 75 |
| 2 4 13 14 | 18 29 43 55 | 22 40 43 59 | 71 75 85 88 | |
| 4 чет (2, 3) | 2 4 13 14 | 18 29 43 55 | 22 40 43 59 | 71 75 85 88 |
| 2 4 13 14 | 18 22 29 40 | 43 43 55 59 | 71 75 85 88 | |
Как отмечалось ранее, количество подзадач соответствует числу
имеющихся процессоров, и поэтому необходимости в проведении
масштабирования вычислений не возникает. Исходное распределение
блоков упорядочиваемого набора данных по процессорам может быть
выбрано совершенно произвольным образом. Для эффективного
выполнения рассмотренного параллельного алгоритма
При анализе
Определим первоначально трудоемкость последовательных
вычислений. При рассмотрении данного вопроса алгоритм T1~n2. Однако применение подобной
оценки сложности последовательного алгоритма приведет к искажению
исходного целевого назначения критериев качества параллельных
вычислений – показатели
Определим теперь сложность рассмотренного параллельного
алгоритма n/p ). Предположим, что данная
начальная
Далее, на каждой выполняемой итерации параллельной p, и, как результат,
общее количество операций этой части параллельных вычислений
оказывается равным$$T_p^2 = 2p(n/p)=2n$$
С учетом полученных соотношений показатели
Расширим приведенные выражения – учтем длительность
выполняемых вычислительных операций и оценим трудоемкость
операции передачи блоков между процессорами. При использовании
модели Хокни общее время всех выполняемых в ходе w есть размер элемента упорядочиваемых данных
в байтах.
С учетом трудоемкости коммуникационных действий общее время
выполнения параллельного алгоритма
Эксперименты осуществлялись на вычислительном кластере
Нижегородского университета на базе процессоров Intel Xeon 4
Для оценки длительности $$\tau$$ базовой скалярной операции алгоритма double, размер которого на данной платформе равен 8 байт
(следовательно w=8 ).
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,002210 | 0,643439 | 0,003270 | 0,434862 |
| 20000 | 0,002991 | 0,004428 | 0,675474 | 0,004596 | 0,650783 |
| 30000 | 0,004612 | 0,006745 | 0,683766 | 0,006873 | 0,671032 |
| 40000 | 0,006297 | 0,008033 | 0,783891 | 0,009107 | 0,691446 |
| 50000 | 0,008014 | 0,009770 | 0,820266 | 0,010840 | 0,739299 |
(рис 9.1) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма пузырьковой сортировкиКак можно заметить из приведенных результатов
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.6) приведено
в таблице 9.3 и на
рис. 9.2.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,002003 | 0,002210 | 0,002057 | 0,003270 |
| 20000 | 0,003709 | 0,004428 | 0,003366 | 0,004596 |
| 30000 | 0,005455 | 0,006745 | 0,004694 | 0,006873 |
| 40000 | 0,007227 | 0,008033 | 0,006035 | 0,009107 |
| 50000 | 0,009018 | 0,009770 | 0,007386 | 0,010840 |
(рис 9.2) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
Общая идея
Общая схема метода состоит в следующем. На первом шаге
алгоритма происходит упорядочивание элементов n/2
пар (ai, an/2+i) для 1<=i<=n/2.
Далее, на втором шаге упорядочиваются элементы в n/4 группах
из четырех элементов (ai, an/4+i, an/2+i, a3n/4+i)
для 1<=i<=n/4. На третьем шаге упорядочиваются элементы уже в n/8 группах
из восьми элементов и т.д. На последнем шаге упорядочиваются
элементы сразу во всем массиве (a1, a2, ..., an). На каждом шаге для
упорядочивания элементов в группах применяется метод log2n.
В более полном виде
Алгоритм 9.4. Последовательный
// Алгоритм 9.4
// Последовательный алгоритм сортировки Шелла
void ShellSort(double A[], int n) {
int incr = n / 2;
while (incr > 0) {
for (int i = incr + 1; i < n; i++) {
int j = i - incr;
while (j > 0)
if (A[j] > A[j + incr]) {
swap(A[j], A[j + incr]);
j = j - incr;
}
else j = 0;
}
incr = incr/2;
}
}
Для N -мерного
гиперкуба (т. е. количество процессоров равно p = 2N ). Выполнение N итераций)
осуществляется взаимодействие процессоров, являющихся соседними в
структуре гиперкуба (но эти процессоры могут оказаться далекими
при линейной нумерации; для установления соответствия двух систем
нумерации процессоров может быть использован, как и ранее, код
Грея – см. лекцию 3). Формирование пар процессоров,
взаимодействующих между собой при выполнении операции "сравнить и
разделить", может быть обеспечено при помощи следующего простого
правила – на каждой итерации i, 0<=i<N, парными становятся
процессоры, у которых различие в битовых представлениях их
номеров имеется только в позиции N-i.
Второй этап состоит в реализации обычных итераций
параллельного алгоритма чет-нечетной перестановки. Итерации
данного этапа выполняются до прекращения фактического изменения
сортируемого набора, и, тем самым, общее количество L таких
итераций может быть различным – от 2 до p.
На рис. 9.3 показан пример
(рис 9.3) Пример работы алгоритма Шелла для 4 процессоровС учетом представленного описания параллельного варианта
Для оценки
Как можно заметить, L – при малом
значении величины L новый параллельный способ
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,002959 | 0,480568 | 0,007509 | 0,189373 |
| 20000 | 0,002991 | 0,004557 | 0,656353 | 0,009826 | 0,304396 |
| 30000 | 0,004612 | 0,006118 | 0,753841 | 0,012431 | 0,371008 |
| 40000 | 0,006297 | 0,008461 | 0,744238 | 0,017009 | 0,370216 |
| 50000 | 0,008014 | 0,009920 | 0,807863 | 0,019419 | 0,412689 |
(рис 9.4) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма сортировки ШеллаСравнение времени выполнения эксперимента $$T^*_p$$ и теоретической
оценки Tp из (9.7) приведено в таблице 9.5 и на рис. 9.5.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 1000 | 0,002684 | 0,002959 | 0,002938 | 0,007509 |
| 20000 | 0,004872 | 0,004557 | 0,004729 | 0,009826 |
| 30000 | 0,007100 | 0,006118 | 0,006538 | 0,012431 |
| 40000 | 0,009353 | 0,008461 | 0,008361 | 0,017009 |
| 50000 | 0,011625 | 0,009920 | 0,010193 | 0,019419 |
(рис 9.5) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
При общем рассмотрении алгоритма log2n итераций исходный массив данных
оказывается упорядоченным. Более подробное изложение метода может
быть получено, например, в [, ].
T1~n2 ). При оптимальном выборе ведущих элементов, когда
разделение каждого блока происходит на равные по размеру части,
трудоемкость алгоритма совпадает с быстродействием наиболее
эффективных способов
Общая схема алгоритма
Алгоритм 9.5. Последовательный алгоритм
// Алгоритм 9.5
// Последовательный алгоритм быстрой сортировки
void QuickSort(double A[], int i1, int i2) {
if (i1 < i2) {
double pivot = A[i1];
int is = i1;
for (int i = i1 + 1; i < i2; i++)
if (A[i] Ј pivot) {
is = is + 1;
swap(A[is], A[i]);
}
swap(A[i1], A[is]);
QuickSort(A, i1, is);
QuickSort(A, is + 1, i2);
}
}
Параллельное обобщение алгоритма N -мерного гиперкуба (т.е. p=2N ). Пусть, как
и ранее, исходный набор данных распределен между процессорами
блоками одинакового размера n/p ; результирующее расположение
блоков должно соответствовать нумерации процессоров гиперкуба.
Возможный способ выполнения первой итерации параллельного метода
при таких условиях может состоять в следующем:
N, и
осуществить взаимообмен данными между этими процессорами.В результате выполнения такой итерации N равен 0. Таких процессоров всего p/2, и, таким
образом, исходный N -мерный гиперкуб также оказывается разделенным
на два гиперкуба размерности N-1. К этим подгиперкубам, в свою
очередь, может быть параллельно применена описанная выше
процедура. После N -кратного повторения подобных итераций для
завершения
Для пояснения на рис. 9.6 представлен пример упорядочивания
данных при n=16, p=4 (т.е. блок каждого процессора содержит 4
элемента). На этом рисунке процессоры изображены в виде
прямоугольников, внутри которых показано содержимое
упорядочиваемых блоков данных; значения блоков приводятся в
начале и при завершении каждой итерации
(рис 9.6) Пример упорядочивания данных параллельным методом быстрой сортировки (без результатов локальной сортировки блоков)Как и ранее, в качестве базовой подзадачи для организации
параллельных вычислений может быть выбрана операция "сравнить и
разделить", а количество подзадач совпадает с числом используемых
процессоров. Распределение подзадач по процессорам должно
производиться с учетом возможности эффективного выполнения
алгоритма при представлении
Оценим трудоемкость рассмотренного параллельного метода. Пусть
у нас имеется N -мерный гиперкуб, состоящий из p=2N процессоров,
где p<n.
Определим вначале вычислительную сложность алгоритма log2p итераций n/p операций (будем
предполагать, что на каждой итерации
При завершении вычислений процессор выполняет (n/p)log2(n/p) операций.
Таким образом, общее время вычислений параллельного алгоритма
Рассмотрим теперь сложность выполняемых коммуникационных
операций. Общее количество межпроцессорных обменов для рассылки
ведущего элемента на N -мерном гиперкубе может быть
ограничено оценкой$$\sum_{i=1}^N i = N(N+1)/2 = \log_2 p (log_2 p+1)/2 \sim (\log_2 p)^2 .$$
При используемых предположениях (выбор ведущих элементов
осуществляется наилучшим образом) количество итераций алгоритма
равно log2p, а объем передаваемых данных между процессорами
всегда равен половине блока, т.е. величине (n/p)/2. При таких
условиях коммуникационная сложность параллельного алгоритма w есть размер элемента набора в байтах.
С учетом всех полученных соотношений общая трудоемкость алгоритма оказывается равной$$T_p=[(n/p)\log_2 p+(n/p)\log_2(n/p)]\tau+ (log_2 p)^2(\alpha+w/ \beta) + \log_2 p(\alpha+w(n/2p)/ \beta).$$
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001521 | 0,934911 | 0,003434 | 0,414094 |
| 20000 | 0,002991 | 0,002234 | 1,338854 | 0,004094 | 0,730581 |
| 30000 | 0,004612 | 0,003080 | 1,497403 | 0,005088 | 0,906447 |
| 40000 | 0,006297 | 0,004363 | 1,443273 | 0,005906 | 1,066204 |
| 50000 | 0,008014 | 0,005486 | 1,460809 | 0,006635 | 1,207837 |
Как можно заметить по результатам
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.11)
приведено в таблице 9.7 и на
рис. 9.8.
(рис 9.7) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма быстрой сортировки| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001280 | 0,001521 | 0,001735 | 0,003434 |
| 20000 | 0,002265 | 0,002234 | 0,002321 | 0,004094 |
| 30000 | 0,003289 | 0,003080 | 0,002928 | 0,005088 |
| 40000 | 0,004338 | 0,004363 | 0,003547 | 0,005906 |
| 50000 | 0,005407 | 0,005486 | 0,004175 | 0,006635 |
(рис 9.8) График зависимости экспериментального и теоретического времени проведения эксперимента на двух процессорах от объема исходных данных
В
Все остальные действия в новом рассматриваемом алгоритме
выполняются в соответствии с обычным методом
При анализе (n/p)/2). Кроме того, в силу
упорядоченности блоков может быть усовершенствована процедура
деления – вместо перебора всех элементов блока теперь достаточно
будет выполнить для ведущего элемента бинарный поиск в блоке. С
учетом всех высказанных замечаний трудоемкость обобщенного
алгоритма
Представим возможный вариант параллельной программы
1. Главная функция программы. Реализует логику работы алгоритма, последовательно вызывает необходимые подпрограммы.
// Программа 9.1.
// Обобщенная быстрая сортировка
int ProcRank; // Ранг текущего процесса
int ProcNum; // Количество процессов
int main(int argc, char *argv[]) {
double *pProcData; // Блок данных процесса
int ProcDataSize; // Размер блока данных
MPI_Init(argc, argv);
MPI_Comm_rank(MPI_COMM_WORLD, ProcRank);
MPI_Comm_size(MPI_COMM_WORLD, ProcNum);
// Инициализация данных и их распределение между процессами
ProcessInitialization(pProcData, ProcDataSize);
// Параллельная сортировка
ParallelHyperQuickSort(pProcData, ProcDataSize);
// Завершение вычислений процесса
ProcessTermination(pProcData, ProcDataSize);
MPI_Finalize();
}
Функция ProcessInitialization определяет исходные данные
решаемой задачи (размер сортируемого массива), выделяет память
для хранения данных, осуществляет генерацию сортируемого массива
(например, при помощи датчика случайных чисел) и распределяет его
между процессами.
Функция ProcessTermination осуществляет
необходимый вывод результатов решения задачи и освобождает всю
ранее выделенную память для хранения данных.
Реализация всех перечисленных функций может быть выполнена по аналогии с ранее рассмотренными примерами и предоставляется читателю в качестве самостоятельного упражнения.
2. Функция ParallelHyperQuickSort.
Функция производит параллельную
// Функция для выполнения обощенного алгоритма быстрой сортировки
void ParallelHyperQuickSort ( double *pProcData,
int ProcDataSize) {
MPI_Status status;
int CommProcRank; // Ранг процессора, с которым выполняется
// взаимодействие
double *pData, // Часть блока, остающаяся на процессоре
*pSendData, // Часть блока, передаваемая процессору
// CommProcRank
*pRecvData, // Часть блока, получаемая от процессора
// CommProcRank
*pMergeData; // Блок данных, получаемый после слияния
int DataSize, SendDataSize, RecvDataSize, MergeDataSize;
int HypercubeDim = (int)ceil(log(ProcNum)/log(2));
// размерность гиперкуба
int Mask = ProcNum;
double Pivot;
// Первоначальная сортировка блоков данных на каждом процессоре
LocalDataSort(pProcData, ProcDataSize);
// Итерации обобщенной быстрой сортировки
for (int i = HypercubeDim; i > 0; i-- ) {
// Определение ведущего значения и его рассылка всем процессорам
PivotDistribution(pProcData, ProcDataSize, HypercubeDim,
Mask, i,Pivot);
Mask = Mask >> 1;
// Определение границы разделения блока
int pos = GetProcDataDivisionPos(pProcData, ProcDataSize, Pivot);
// Разделение блока на части
if ( ( (rank Mask) >> (i - 1) ) == 0 ) { // старший бит = 0
pSendData = pProcData[pos + 1];
SendDataSize = ProcDataSize - pos – 1;
if (SendDataSize < 0) SendDataSize = 0;
CommProcRank = ProcRank + Mask
pData = pProcData[0];
DataSize = pos + 1;
}
else { // старший бит = 1
pSendData = pProcData[0];
SendDataSize = pos + 1;
if (SendDataSize > ProcDataSize) SendDataSize = pos;
CommProcRank = ProcRank – Mask
pData = pProcData[pos + 1];
DataSize = ProcDataSize - pos - 1;
if (DataSize < 0) DataSize = 0;
}
// Пересылка размеров частей блоков данных
MPI_Sendrecv(SendDataSize, 1, MPI_INT, CommProcRank, 0,
RecvDataSize, 1, MPI_INT, CommProcRank, 0, MPI_COMM_WORLD, status);
// Пересылка частей блоков данных
pRecvData = new double[RecvDataSize];
MPI_Sendrecv(pSendData, SendDataSize, MPI_DOUBLE,
CommProcRank, 0, pRecvData, RecvDataSize, MPI_DOUBLE,
CommProcRank, 0, MPI_COMM_WORLD, status);
// Слияние частей
MergeDataSize = DataSize + RecvDataSize;
pMergeData = new double[MergeDataSize];
DataMerge(pMergeData, pMergeData, pData, DataSize,
pRecvData, RecvDataSize);
delete [] pProcData;
delete [] pRecvData;
pProcData = pMergeData;
ProcDataSize = MergeDataSize;
}
}
Функция LocalDataSort выполняет
Функция PivotDistribution определяет ведущий
элемент и рассылает его значение всем процессорам.
Функция GetProcDataDivisionPos выполняет
разделение блока данных относительно ведущего элемента. Ее
результатом является целое число, обозначающее позицию элемента
на границе двух блоков.
Функция DataMerge осуществляет слияние частей в
один упорядоченный блок данных.
3. Функция PivotDistribution. Функция
выбирает ведущий элемент и рассылает его все процессорам
гиперкуба. Так как данные на процессорах отсортированы с самого
начала, ведущий элемент выбирается как средний элемент блока
данных.
// Функция выбора и рассылки ведущего элемента
void PivotDistribution (double *pProcData, int ProcDataSize, int Dim,
int Mask, int Iter, double *pPivot) {
MPI_Group WorldGroup;
MPI_Group SubcubeGroup; // Группа процессов — подгиперкуб
MPI_Comm SubcubeComm; // Коммуникатор подгиперкуба
int j = 0;
int GroupNum = ProcNum /(int)pow(2, Dim-Iter);
int *ProcRanks = new int [GroupNum];
// формирование списка рангов процессов для гиперкуба
int StartProc = ProcRank – GroupNum;
if (StartProc < 0) StartProc = 0;
int EndProc = ProcRank + GroupNum;
if (EndProc > ProcNum) EndProc = ProcNum;
for (int proc = StartProc; proc < EndProc; proc++) {
if ((ProcRank Mask)>>(Iter) == (proc Mask)>>(Iter)) {
ProcRanks[j++] = proc;
}
}
// Объединение процессов подгиперкуба в одну группу
MPI_Comm_group(MPI_COMM_WORLD, WorldGroup);
MPI_Group_incl(WorldGroup, GroupNum, ProcRanks, SubcubeGroup);
MPI_Comm_create(MPI_COMM_WORLD, SubcubeGroup, SubcubeComm);
// Поиск и рассылка ведущего элемента всем процессам подгиперкуба
if (ProcRank == ProcRanks[0])
*pPivot = pProcData[ProcDataSize / 2];
MPI_Bcast(pPivot, 1, MPI_DOUBLE, 0, SubcubeComm);
MPI_Group_free(SubcubeGroup);
MPI_Comm_free(SubcubeComm);
delete [] ProcRanks;
}
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001485 | 0,957576 | 0,002898 | 0,490683 |
| 20000 | 0,002991 | 0,002180 | 1,372018 | 0,003770 | 0,793369 |
| 30000 | 0,004612 | 0,003077 | 1,498863 | 0,004451 | 1,036172 |
| 40000 | 0,006297 | 0,003859 | 1,631770 | 0,004721 | 1,333828 |
| 50000 | 0,008014 | 0,005041 | 1,589764 | 0,005242 | 1,528806 |
(рис 9.9) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма обобщенной быстрой сортировкиСравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.12)
приведено в таблице 9.9 и на
рис. 9.10.
| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001281 | 0,001485 | 0,001735 | 0,002898 |
| 20000 | 0,002265 | 0,002180 | 0,002322 | 0,003770 |
| 30000 | 0,003289 | 0,003077 | 0,002928 | 0,004451 |
| 40000 | 0,004338 | 0,003859 | 0,003547 | 0,004721 |
| 50000 | 0,005407 | 0,005041 | 0,004176 | 0,005242 |
(рис 9.10) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных
Алгоритм
0, m, 2m, ...,(p-1)m, где m=n/p2 ;p
частей с использованием полученного набора ведущих значений;j,
0<=j<p, каждого блока пересылается процессору с
номером j ;По завершении четвертого этапа исходный набор данных становится отсортированным.
На рис. 9.11 приведен
пример
(рис 9.11) Пример работы алгоритма сортировки с использованием регулярного набора образцов
Оценим трудоемкость рассмотренного параллельного метода.
Пусть, как и ранее, n есть количество сортируемых
данных, p, p<n, обозначает число используемых
процессоров и, соответственно, n/p есть размер
блоков данных на процессорах.
В течение первого этапа алгоритма каждый процессор сортирует
свой блок данных с помощью
На втором этапе алгоритма один из процессоров собирает наборы
из p элементов со всех остальных процессоров, выполняет слияние
всех полученных данных (общее количество элементов составляет p2 ), формирует набор из p-1 ведущих элементов и рассылает
полученный набор всем остальным процессорам. С учетом всех
перечисленных действий общая длительность второго этапа
составляет$$T_p^2=[\alpha\log_2p+wp(p-1)/ \beta]+
[p^2\log_2p\tau]+[p\tau]+
[\log_2p(\alpha+wp/ \beta)]$$
(в приведенном соотношении выделенные подвыражения соответствуют
четырем перечисленным действиям алгоритма); здесь, как и ранее, $$\alpha$$ – латентность, $$\beta$$ – пропускная способность сети
передачи данных, а w есть размер элемента
упорядочиваемых данных в байтах.
В ходе выполнения третьего этапа алгоритма каждый процессор
разделяет свои элементы относительно ведущих элементов на p
частей (общее количество операций для этого может быть ограничено
величиной n/p ). Далее все процессоры выполняют рассылку
сформированных частей блоков между собой – оценка трудоемкости
такой коммуникационной операции рассмотрена в лекции 3 при
представлении топологии вычислительной сети в виде гиперкуба. Как
было показано, выполнение такой операции может быть осуществлено
за log2p шагов, на каждом из которых каждый процессор передает и
получает сообщение из (n/p)/2 элементов. Как результат, общая
трудоемкость третьего этапа алгоритма может быть оценена как$$T_p^3=(n/p)\tau+\log_2p(\alpha+w(n/2p)/ \beta).$$
На четвертом этапе алгоритма каждый процессор выполняет
слияние p отсортированных частей в один объединенный блок. Оценка
трудоемкости такой операции уже проводилась при рассмотрении
второго этапа, и, тем самым, длительность выполнения процедуры
слияния составляет$$T_p^4=(n/p)\log_2p\tau.$$
С учетом всех полученных соотношений общее время выполнения
алгоритма
Результаты
| Количество элементов | Последовательный алгоритм | Параллельный алгоритм | |||
|---|---|---|---|---|---|
| 2 процессора | 4 процессора | ||||
| Время | Ускорение | Время | Ускорение | ||
| 10000 | 0,001422 | 0,001513 | 0,939855 | 0,001166 | 1,219554 |
| 20000 | 0,002991 | 0,002307 | 1,396489 | 0,002081 | 1,437290 |
| 30000 | 0,004612 | 0,003168 | 1,455808 | 0,003099 | 1,488222 |
| 40000 | 0,006297 | 0,004542 | 1,386394 | 0,003819 | 1,648861 |
| 50000 | 0,008014 | 0,005503 | 1,456297 | 0,004370 | 1,833867 |
(рис 9.12) Зависимость ускорения от количества процессоров при выполнении параллельного алгоритма сортировки с использованием регулярного набора образцов| Количество элементов | Параллельный алгоритм | |||
|---|---|---|---|---|
| 2 процессора | 4 процессора | |||
| $$T_2$$ | $$T_2^*$$ | $$T_4$$ | $$T_4^*$$ | |
| 10000 | 0,001533 | 0,001513 | 0,001762 | 0,001166 |
| 20000 | 0,002569 | 0,002307 | 0,002375 | 0,002081 |
| 30000 | 0,003645 | 0,003168 | 0,003007 | 0,003099 |
| 40000 | 0,004747 | 0,004542 | 0,003652 | 0,003819 |
| 50000 | 0,005867 | 0,005503 | 0,004307 | 0,004370 |
Сравнение времени выполнения эксперимента $$T^*_p$$ и
теоретической оценки Tp из (9.17)
приведено в таблице 9.11 и на
рис. 9.13.
(рис 9.13) График зависимости экспериментального и теоретического времени проведения эксперимента на четырех процессорах от объема исходных данных
В лекции рассматривается часто встречающаяся в приложениях задача упорядочения данных, для решения которой в рамках данного
учебного материала выбраны широко известные алгоритмы
Алгоритм
Для алгоритма Шелла (подраздел 9.4) рассматривается схема
распараллеливания при представлении топологии сети в виде
гиперкуба. При таком представлении топологии оказывается
возможной организация взаимодействия процессоров, расположенных
далеко друг от друга при линейной нумерации. Как правило, такая
организация вычислений позволяет уменьшить количество выполняемых
итераций алгоритма
Для алгоритма
При применении алгоритма
Возможные способы решения задачи упорядочения данных широко обсуждаются в литературе; один из наиболее полных обзоров ], среди последних изданий может быть рекомендована работа [].
Параллельные варианты алгоритма
Схемы распараллеливания
Полезной при рассмотрении вопросов параллельных вычислений для
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.