Изменения

Перейти к: навигация, поиск

Обсуждение участника:AKhimulya

1976 байт добавлено, 06:22, 4 июня 2014
Нет описания правки
== Многопоточная сортировка слиянием ==
Благодаря тому, что сортировка слиянием построена на принципе "Разделяй и властвуй", выполнение данного алгоритма можно весьма эффективно распараллелить. При оценке асимптотики допускается, что возможен запуск неограниченного количества независимых процессов, т.е. процессов с вычислительными ресурсами, не зависящими от других процессов, что на практике не достижимо. Более того, при реализации имеет смысл ограничить количество параллельных потоков.
===Сортировка с однопоточным слиянием===
Внесем в алгоритм сортировки слиянием следующую модификацию: будем сортировать левую и правую части массива параллельно.
В данном алгоритме оператор '''spawn''' запускает новый поток, а оператор '''sync''' ожидает завершения этого потока. Функция merge аналогична функции merge из раздела [http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5%D0%BC#.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>
Несмотря на наличие двух рекурсивных вызовов, при оценке будем считать, что совершается один вызов, т.к. оба вызова выполняются параллельно с одинаковой асимптотикой. Оценим время работы данного алгоритма: <mathtex>T(n) = T(n / 2) + \Theta(n) = \Theta(n)</mathtex>. Данная асимптотика достигается при возможности запускать неограниченное количество потоков независимо друг от друга.<br>
===Многопоточное слияние===
Как видно из оценки первого алгоритма, слияние выполняется слишком долго при том, что существует возможность является его ускоритьузким местом. Рассмотрим Попытаемся распараллелить слияние, для чего рассмотрим алгоритм рекурсивного слияния массивов <mathtex>T[left_{1} \dots right_{1}]</mathtex> и <mathtex>T[left_{2} \dots right_{2}]</mathtex> в массив <mathtex>A[left_{3} \dots right_{3}]</mathtex>:# Убедимся, что размер <mathtex>T[left_{1} \dots right_{1}]</mathtex> больше либо равен размеру <mathtex>T[left_{2} \dots right_{2}]</mathtex># Вычислим Возьмем <mathtex>x = T[mid_{1}]</mathtex> - середину первого массива (<mathtex>x</mathtex> также является и медианой этого массива)# При помощи бинарного поиска найдем <mathtex>mid_{2}</mathtex> такое, что <mathtex>\forall y \in T[left_{2} \dots mid_{2} - 1]: y < x</mathtex># <mathtex>mid_{3} = left_{3} + (mid_{1} - left_{1}) + (mid_{2} - left_{2})</mathtex># <mathtex>A[mid_{3}] = x</mathtex># Сольем <mathtex>T[right_{1} \dots mid_{1} - 1]</mathtex> и <mathtex>T[right_{2} \dots mid_{2}]</mathtex> в <mathtex>A[right_{3} \dots mid_{3} - 1]</mathtex># Сольем <mathtex>T[mid_{1} + 1 \dots right_{1}]</mathtex> и <mathtex>T[mid_{2} \dots right_{2}]</mathtex> в <mathtex>A[mid_{3} + 1 \dots right_{3}]</mathtex>
Рассмотрим псевдокод данного алгоритма:
// если <mathtex>right \leqslant left</mathtex> возвращает <mathtex>left</mathtex> // если <mathtex>x \leqslant T[left]</mathtex>, возвращает <mathtex>left</mathtex> // иначе возвращает наибольший индекс <mathtex>i</mathtex> из отрезка <mathtex>[left; right]</mathtex> такой, что <mathtex>array[i - 1] < x</mathtex>
binarySearch(x, array, left, right)
// слияние <mathtex>T[left_{1} \dots right_{1}]</mathtex> и <mathtex>T[left_{2} \dots right_{2}]</mathtex> в <mathtex>A[left_{3} \dots right_{1} - left_{1} + right_{2} - left_{2}]</mathtex> mergeMT(T, left<mathtex>_{1}</mathtex>, right<mathtex>_{1}</mathtex>, left<mathtex>_{2}</mathtex>, right<mathtex>_{2}</mathtex>, A, left<mathtex>_{3}</mathtex>): n<mathtex>_{1}</mathtex> = right<mathtex>_{1}</mathtex> - left<mathtex>_{1}</mathtex> + 1 n<mathtex>_{2}</mathtex> = right<mathtex>_{2}</mathtex> - left<mathtex>_{2}</mathtex> + 1 '''if ''' n<mathtex>_{1}</mathtex> < n<mathtex>_{2}</mathtex>: swap(left<mathtex>_{1}</mathtex>, left<mathtex>_{2}</mathtex>) swap(right<mathtex>_{1}</mathtex>, right<mathtex>_{2}</mathtex>) swap(n<mathtex>_{1}</mathtex>, n<mathtex>_{2}</mathtex>)
'''if ''' n<mathtex>_{1}</mathtex> == 0: '''return''' '''else''' mid<mathtex>_{1}</mathtex> = (left<mathtex>_{1}</mathtex> + right<mathtex>_{1}</mathtex>) / 2 mid<mathtex>_{2}</mathtex> = binarySearch(T[mid<mathtex>_{1}</mathtex>], T, left<mathtex>_{2}</mathtex>, right<mathtex>_{2}</mathtex>) mid<mathtex>_{3}</mathtex> = left<mathtex>_{3}</mathtex> + (mid<mathtex>_{1}</mathtex> - left<mathtex>_{1}</mathtex>) + (mid<mathtex>_{2}</mathtex> - left<mathtex>_{2}</mathtex>) A[mid<mathtex>_{3}</mathtex>] = T[mid<mathtex>_{1}</mathtex>] '''spawn''' mergeMT(T, left<mathtex>_{1}</mathtex>, mid<mathtex>_{1}</mathtex> - 1, left<mathtex>_{2}</mathtex>, mid<mathtex>_{2}</mathtex> - 1, A, left<mathtex>_{3}</mathtex>) mergeMT(T, mid<mathtex>_{1}</mathtex> + 1, right<mathtex>_{1}</mathtex>, mid<mathtex>_{2}</mathtex>, right<mathtex>_{2}</mathtex>, A, mid<mathtex>_{3}</mathtex> + 1)
'''sync'''
Оценим время выполнения данного алгоритма сверху при возможности запускать неограниченное количество потоков независимо друг от друга. Оба массива содержат <mathtex>n_{1} + n_{2} = n</mathtex> элементов. К моменту рекурсивных вызовов <mathtex>n_{2} \leqslant n_{1}</mathtex>, значит, <mathbr><tex>n_{2} = 2 * \cdot n_{2} / 2 \leqslant (n_{1} + n_{2}) / 2 = n / 2</mathtex>. <br> В худшем случае один из двух рекурсивных вызовов сольет <mathtex>n_{1} / 2</mathtex> элементов <mathtex>T[left_{1} \dots right_{1}]</mathtex> с <mathtex>n_{2}</mathtex> элементами <mathtex>T[left_{2} \dots right_{2}]</mathtex> и тогда количество элементов первых двух массивов в рекурсивном вызове будет равно <mathbr><tex>n_{1} / 2 + n_{2} \leqslant n_{1} / 2 + n_{2} / 2 + n_{2} / 2 = (n_{1} + n_{2}) / 2 + n_{2} / 2 \leqslant n / 2 + n / 4 = 3 * \cdot n / 4</mathtex>.<br>Асимптотика каждого вызова функции - <tex>\Theta(\log(n))</tex>, т.е. время, затрачиваемое на бинарный поиск. Так как рекурсивные вызовы функции выполняются параллельно, а потоки при оценке независимы, время их выполнения будет равно времени выполнения самого долгого вызова. В худшем случае это <mathtex>T(3 * \cdot n / 4)</mathtex>. Тогда суммарное время работы алгоритма слияния будет равно получим оценку сверху<br><mathtex>TmergeT_{merge}(n) = TmergeT_{merge}(3 * \cdot n / 4) + \Theta(\log(n)) = \Theta(\log^2(n))</math>, т.к. асимптотика каждого вызова функции - <mathtex>\Theta(\log(n))</math>, т.е. время, затрачиваемое на бинарный поиск.
===Сортировка с многопоточным слиянием===
Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы <mathtex>A[leftA \dots rightA]</mathtex> и помещающего отсортированный массив в <mathtex>B[leftB \dots leftB + rightA - leftA]</mathtex>
mergeSortMT2(A, leftA, rightA, B, leftB):
n = r - p + 1
'''if ''' n == 1:
B[leftB] = A[leftA]
'''else''' создадим новый массив T[1 <mathtex>\dots</mathtex> n]
mid = (leftA + rightA) / 2
newMid = mid - leftA + 1
mergeMT(T, 1, newMid, newMid + 1, n, B, leftB)
Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов <mathtex>TmergeSortT_{mergeSort}(n) = TmergeSortT_{mergeSort}(n / 2) + TmergeT_{merge}(n) = TmergeSortT_{mergeSort}(n / 2) + \Theta(\log^2(n)) = \Theta(\log^3(n))</mathtex>.
===Реализация===
Очевидно, что при отсутствии возможности запуска неограниченного количества независимых потоков, вычислительная сложность многопоточного алгоритма зависит от максимально возможного количества независимых потоков. Обозначим такое количество как <tex>N_{ind}</tex>. Оценим приведенные выше алгоритмы с учетом наложенных ограничений:<br>
::'''Сортировка с однопоточным слиянием''' будет иметь асимптотику <tex>\Theta(n/N_{ind}\log(n/N_{ind}) + n)</tex>:
::::<tex>\Theta(n/N_{ind}\log(n/N_{ind}))</tex> операций нужно на последовательную сортировку массива длиной <tex>n/N_{ind}</tex>.
::::<tex>\Theta(n)</tex> необходимо на последовательное слияние.
::Соответветственно, асимптотика может быть как <tex>\Theta(n/N_{ind}\log(n/N_{ind}))</tex>, так и <tex>\Theta(n)</tex> в зависимости от <tex>N_{ind}</tex> и <tex>n</tex>
::'''Многопоточное слияние''' будет работать за <tex>\Theta((\frac{n}{N_{ind}})^{(\log_{\frac{4}{3}}2)} + \log(n) \cdot min(N_{ind}, \log(n))</tex>
::::Прежде чем достигнуть ограничения на создание нового потока, алгоритм углубится на <tex>min(N_{ind}, \log(n))</tex> уровней вглубь дерева рекурсии, где на каждом уровне выполняется бинпоиск за <tex>\Theta(\log(n))</tex>
::::Асимптотика многопоточного слияния при работе в одном потоке по основной теореме рекуррентных соотношений равна
::::<tex>T_{merge}'(n) = 2T_{merge}'(\frac {3}{4}n) + \Theta(\log(n)) = \Theta(n^{(\log_{\frac{4}{3}}2)})</tex>
===Литература===
Cormen T.H., Leiserson C.E., Rivest R.L., Stein C. {{- --}} Introduction to Algorithms, Third Edition
97
правок

Навигация