Обсуждение участника:AKhimulya — различия между версиями
AKhimulya (обсуждение | вклад) |
AKhimulya (обсуждение | вклад) м |
||
Строка 4: | Строка 4: | ||
Внесем в алгоритм сортировки слиянием следующую модификацию: будем сортировать левую и правую части массива параллельно. | Внесем в алгоритм сортировки слиянием следующую модификацию: будем сортировать левую и правую части массива параллельно. | ||
− | + | mergeSortMT(array, left, right): | |
mid = (left + right) / 2 | mid = (left + right) / 2 | ||
− | + | '''spawn''' mergeSortMT(array, left, mid) | |
− | + | mergeSortMT(array, mid + 1, right) | |
− | + | '''sync''' | |
− | + | merge(array, left, mid, right) | |
В данном алгоритме оператор <tex>\mathrm {\bf {spawn}}</tex> запускает новый поток, а оператор <tex>\mathrm {\bf {sync}}</tex> ожидает завершения этого потока. Функция <tex>\mathrm {merge}</tex> аналогична одноименной функции из раздела [[Сортировка слиянием#.D0.A1.D0.BB.D0.B8.D1.8F.D0.BD.D0.B8.D0.B5_.D0.B4.D0.B2.D1.83.D1.85_.D0.BC.D0.B0.D1.81.D1.81.D0.B8.D0.B2.D0.BE.D0.B2|слияние двух массивов]].<br> | В данном алгоритме оператор <tex>\mathrm {\bf {spawn}}</tex> запускает новый поток, а оператор <tex>\mathrm {\bf {sync}}</tex> ожидает завершения этого потока. Функция <tex>\mathrm {merge}</tex> аналогична одноименной функции из раздела [[Сортировка слиянием#.D0.A1.D0.BB.D0.B8.D1.8F.D0.BD.D0.B8.D0.B5_.D0.B4.D0.B2.D1.83.D1.85_.D0.BC.D0.B0.D1.81.D1.81.D0.B8.D0.B2.D0.BE.D0.B2|слияние двух массивов]].<br> | ||
Строка 31: | Строка 31: | ||
// если <tex dpi="120">x \leqslant T[left]</tex>, возвращает <tex dpi="120">left</tex> | // если <tex dpi="120">x \leqslant T[left]</tex>, возвращает <tex dpi="120">left</tex> | ||
// иначе возвращает наибольший индекс <tex dpi="120">i</tex> из отрезка <tex dpi="120">[left; right]</tex> такой, что <tex dpi="120">array[i - 1] < x</tex> | // иначе возвращает наибольший индекс <tex dpi="120">i</tex> из отрезка <tex dpi="120">[left; right]</tex> такой, что <tex dpi="120">array[i - 1] < x</tex> | ||
− | + | binarySearch(x, array, left, right) | |
// слияние <tex dpi="120">T[left_{1} \dots right_{1}]</tex> и <tex dpi="120">T[left_{2} \dots right_{2}]</tex> в <tex dpi="120">A[left_{3} \dots right_{1} - left_{1} + right_{2} - left_{2}]</tex> | // слияние <tex dpi="120">T[left_{1} \dots right_{1}]</tex> и <tex dpi="120">T[left_{2} \dots right_{2}]</tex> в <tex dpi="120">A[left_{3} \dots right_{1} - left_{1} + right_{2} - left_{2}]</tex> | ||
− | + | mergeMT(T, left<tex dpi="120">_{1}</tex>, right<tex dpi="120">_{1}</tex>, left<tex dpi="120">_{2}</tex>, right<tex dpi="120">_{2}</tex>, A, left<tex dpi="120">_{3}</tex>): | |
n<tex dpi="120">_{1}</tex> = right<tex dpi="120">_{1}</tex> - left<tex dpi="120">_{1}</tex> + 1 | n<tex dpi="120">_{1}</tex> = right<tex dpi="120">_{1}</tex> - left<tex dpi="120">_{1}</tex> + 1 | ||
n<tex dpi="120">_{2}</tex> = right<tex dpi="120">_{2}</tex> - left<tex dpi="120">_{2}</tex> + 1 | n<tex dpi="120">_{2}</tex> = right<tex dpi="120">_{2}</tex> - left<tex dpi="120">_{2}</tex> + 1 | ||
− | + | '''if''' n<tex dpi="120">_{1}</tex> < n<tex dpi="120">_{2}</tex> | |
− | + | swap(left<tex dpi="120">_{1}</tex>, left<tex dpi="120">_{2}</tex>) | |
− | + | swap(right<tex dpi="120">_{1}</tex>, right<tex dpi="120">_{2}</tex>) | |
− | + | swap(n<tex dpi="120">_{1}</tex>, n<tex dpi="120">_{2}</tex>) | |
− | + | '''if''' n<tex dpi="120">_{1}</tex> == 0 | |
− | + | '''return''' | |
− | + | '''else''' | |
mid<tex dpi="120">_{1}</tex> = (left<tex dpi="120">_{1}</tex> + right<tex dpi="120">_{1}</tex>) / 2 | mid<tex dpi="120">_{1}</tex> = (left<tex dpi="120">_{1}</tex> + right<tex dpi="120">_{1}</tex>) / 2 | ||
mid<tex dpi="120">_{2}</tex> = binarySearch(T[mid<tex dpi="120">_{1}</tex>], T, left<tex dpi="120">_{2}</tex>, right<tex dpi="120">_{2}</tex>) | mid<tex dpi="120">_{2}</tex> = binarySearch(T[mid<tex dpi="120">_{1}</tex>], T, left<tex dpi="120">_{2}</tex>, right<tex dpi="120">_{2}</tex>) | ||
mid<tex dpi="120">_{3}</tex> = left<tex dpi="120">_{3}</tex> + (mid<tex dpi="120">_{1}</tex> - left<tex dpi="120">_{1}</tex>) + (mid<tex dpi="120">_{2}</tex> - left<tex dpi="120">_{2}</tex>) | mid<tex dpi="120">_{3}</tex> = left<tex dpi="120">_{3}</tex> + (mid<tex dpi="120">_{1}</tex> - left<tex dpi="120">_{1}</tex>) + (mid<tex dpi="120">_{2}</tex> - left<tex dpi="120">_{2}</tex>) | ||
A[mid<tex dpi="120">_{3}</tex>] = T[mid<tex dpi="120">_{1}</tex>] | A[mid<tex dpi="120">_{3}</tex>] = T[mid<tex dpi="120">_{1}</tex>] | ||
− | + | '''spawn''' mergeMT(T, left<tex dpi="120">_{1}</tex>, mid<tex dpi="120">_{1}</tex> - 1, left<tex dpi="120">_{2}</tex>, mid<tex dpi="120">_{2}</tex> - 1, A, left<tex dpi="120">_{3}</tex>) | |
− | + | mergeMT(T, mid<tex dpi="120">_{1}</tex> + 1, right<tex dpi="120">_{1}</tex>, mid<tex dpi="120">_{2}</tex>, right<tex dpi="120">_{2}</tex>, A, mid<tex dpi="120">_{3}</tex> + 1) | |
− | + | '''sync''' | |
Оба массива содержат <tex dpi="120">n_{1} + n_{2} = n</tex> элементов. К моменту рекурсивных вызовов <tex dpi="120">n_{2} \leqslant n_{1}</tex>, значит,<br> | Оба массива содержат <tex dpi="120">n_{1} + n_{2} = n</tex> элементов. К моменту рекурсивных вызовов <tex dpi="120">n_{2} \leqslant n_{1}</tex>, значит,<br> | ||
Строка 61: | Строка 61: | ||
Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы <tex dpi="120">A[leftA \dots rightA]</tex> и помещающего отсортированный массив в <tex dpi="120">B[leftB \dots leftB + rightA - leftA]</tex> | Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы <tex dpi="120">A[leftA \dots rightA]</tex> и помещающего отсортированный массив в <tex dpi="120">B[leftB \dots leftB + rightA - leftA]</tex> | ||
− | + | mergeSortMT2(A, leftA, rightA, B, leftB): | |
n = r - p + 1 | n = r - p + 1 | ||
− | + | '''if''' n == 1 | |
B[leftB] = A[leftA] | B[leftB] = A[leftA] | ||
− | + | '''else''' | |
создадим новый массив T[1 <tex dpi="120">\dots</tex> n] | создадим новый массив T[1 <tex dpi="120">\dots</tex> n] | ||
mid = (leftA + rightA) / 2 | mid = (leftA + rightA) / 2 | ||
newMid = mid - leftA + 1 | newMid = mid - leftA + 1 | ||
− | + | '''spawn''' mergeSortMT2(A, leftA, mid, T, 1) | |
− | + | mergeSortMT2(A, mid + 1, rightA, T, newMid + 1) | |
− | + | '''sync''' | |
− | + | mergeMT(T, 1, newMid, newMid + 1, n, B, leftB) | |
Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов <tex dpi="130">T_{\mathrm {mergeSort}}(n) = T_{\mathrm {mergeSort}}(\frac{n}{2}) + T_{\mathrm {merge}}(n) = T_{\mathrm {mergeSort}}(\frac{n}{2}) + \Theta(\log^2 n) = \Theta(\log^3 n)</tex>. | Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов <tex dpi="130">T_{\mathrm {mergeSort}}(n) = T_{\mathrm {mergeSort}}(\frac{n}{2}) + T_{\mathrm {merge}}(n) = T_{\mathrm {mergeSort}}(\frac{n}{2}) + \Theta(\log^2 n) = \Theta(\log^3 n)</tex>. |
Версия 03:51, 7 июня 2014
Содержание
Многопоточная сортировка слиянием
Благодаря тому, что сортировка слиянием построена на принципе "Разделяй и властвуй", выполнение данного алгоритма можно весьма эффективно распараллелить. При оценке асимптотики допускается, что возможен запуск неограниченного количества независимых процессов, т.е. процессов с вычислительными ресурсами, не зависящими от других процессов, что на практике не достижимо. Более того, при реализации имеет смысл ограничить количество параллельных потоков.
Сортировка с однопоточным слиянием
Внесем в алгоритм сортировки слиянием следующую модификацию: будем сортировать левую и правую части массива параллельно.
mergeSortMT(array, left, right): mid = (left + right) / 2 spawn mergeSortMT(array, left, mid) mergeSortMT(array, mid + 1, right) sync merge(array, left, mid, right)
В данном алгоритме оператор слияние двух массивов.
Несмотря на наличие двух рекурсивных вызовов, при оценке будем считать, что совершается один вызов, т.к. оба вызова выполняются параллельно с одинаковой асимптотикой. Оценим время работы данного алгоритма: . Данная асимптотика достигается при возможности запускать неограниченное количество потоков независимо друг от друга.
Многопоточное слияние
Как видно из оценки первого алгоритма, слияние является его узким местом. Попытаемся распараллелить слияние, для чего рассмотрим алгоритм рекурсивного слияния массивов
и в массив :- Убедимся, что размер больше либо равен размеру
- Возьмем - середину первого массива ( также является и медианой этого массива)
- При помощи бинарного поиска найдем такое, что
- Сольем и в
- Сольем и в
Рассмотрим псевдокод данного алгоритма:
// есливозвращает // если , возвращает // иначе возвращает наибольший индекс из отрезка такой, что binarySearch(x, array, left, right) // слияние и в mergeMT(T, left , right , left , right , A, left ): n = right - left + 1 n = right - left + 1 if n < n swap(left , left ) swap(right , right ) swap(n , n ) if n == 0 return else mid = (left + right ) / 2 mid = binarySearch(T[mid ], T, left , right ) mid = left + (mid - left ) + (mid - left ) A[mid ] = T[mid ] spawn mergeMT(T, left , mid - 1, left , mid - 1, A, left ) mergeMT(T, mid + 1, right , mid , right , A, mid + 1) sync
Оба массива содержат
.
В худшем случае один из двух рекурсивных вызовов сольет элементов с элементами и тогда количество элементов первых двух массивов в рекурсивном вызове будет равно
.
Асимптотика каждого вызова функции - , т.е. время, затрачиваемое на бинарный поиск. Так как рекурсивные вызовы функции выполняются параллельно, а потоки при оценке независимы, время их выполнения будет равно времени выполнения самого долгого вызова. В худшем случае это . Тогда получим оценку сверху
Сортировка с многопоточным слиянием
Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы
и помещающего отсортированный массив в mergeSortMT2(A, leftA, rightA, B, leftB):
n = r - p + 1
if n == 1
B[leftB] = A[leftA]
else
создадим новый массив T[1
n]
mid = (leftA + rightA) / 2
newMid = mid - leftA + 1
spawn mergeSortMT2(A, leftA, mid, T, 1)
mergeSortMT2(A, mid + 1, rightA, T, newMid + 1)
sync
mergeMT(T, 1, newMid, newMid + 1, n, B, leftB)
Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов
.Оценка при фиксированном числе потоков
Очевидно, что при отсутствии возможности запуска неограниченного количества независимых потоков, вычислительная сложность многопоточного алгоритма зависит от максимально возможного количества независимых потоков. Обозначим такое количество как
- Сортировка с однопоточным слиянием будет иметь асимптотику :
- операций нужно на последовательную сортировку массива длиной .
- необходимо на последовательное слияние.
- Многопоточное слияние будет работать за :
- Прежде чем достигнуть ограничения на создание нового потока, алгоритм углубится на уровней вглубь дерева рекурсии, где на каждом уровне выполняется бинпоиск за
- Асимптотика многопоточного слияния при работе в одном потоке по основной теореме рекуррентных соотношений равна
- Оценим сортировку с многопоточным слиянием снизу:
- Части массива длиной гарантированно будут сортироваться последовательно, т.к. только алгоритм сортировки запустит к моменту вызова от массива длиной число потоков, равное . Тогда по основной теореме рекуррентных соотношений:
- Сортировка с однопоточным слиянием будет иметь асимптотику :
Очевидно, что нижняя оценка алгоритма сортировки с многопоточным слиянием выше. Таким образом, при приведенных выше допущениях алгоритм сортировки с однопоточным слиянием эффективнее и его асимптотика составляет
.Литература
Cormen T.H., Leiserson C.E., Rivest R.L., Stein C. — Introduction to Algorithms, Third Edition