Обсуждение участника:AKhimulya — различия между версиями
AKhimulya (обсуждение | вклад) м |
AKhimulya (обсуждение | вклад) |
||
Строка 72: | Строка 72: | ||
'''sync''' | '''sync''' | ||
− | Оценим время выполнения данного алгоритма сверху при возможности запускать неограниченное количество потоков независимо друг от друга. Оба массива содержат <math>n_{1} + n_{2} = n</math> элементов. К моменту рекурсивных вызовов <math>n_{2} <= n_{1}</math>, значит, <math>n_{2} = 2 * n_{2} / 2 <= (n_{1} + n_{2}) / 2 = n / 2</math>. В худшем случае один из двух рекурсивных вызовов сольет <math>n_{1} / 2</math> элементов <math>T[left_{1} \dots right_{1}]</math> с <math>n_{2}</math> элементами <math>T[left_{2} \dots right_{2}]</math> и тогда количество элементов первых двух массивов в рекурсивном вызове будет равно <math>n_{1} / 2 + n_{2} <= n_{1} / 2 + n_{2} / 2 + n_{2} / 2 = (n_{1} + n_{2}) / 2 + n_{2} / 2 <= n / 2 + n / 4 = 3 * n / 4</math>. Так как рекурсивные вызовы функции выполняются параллельно, время их выполнения будет равно времени выполнения самого долгого вызова. В худшем случае это <math>T(3 * n / 4)</math>. Тогда сумарное время работы алгоритма слияния будет равно <math> | + | Оценим время выполнения данного алгоритма сверху при возможности запускать неограниченное количество потоков независимо друг от друга. Оба массива содержат <math>n_{1} + n_{2} = n</math> элементов. К моменту рекурсивных вызовов <math>n_{2} <= n_{1}</math>, значит, <math>n_{2} = 2 * n_{2} / 2 <= (n_{1} + n_{2}) / 2 = n / 2</math>. В худшем случае один из двух рекурсивных вызовов сольет <math>n_{1} / 2</math> элементов <math>T[left_{1} \dots right_{1}]</math> с <math>n_{2}</math> элементами <math>T[left_{2} \dots right_{2}]</math> и тогда количество элементов первых двух массивов в рекурсивном вызове будет равно <math>n_{1} / 2 + n_{2} <= n_{1} / 2 + n_{2} / 2 + n_{2} / 2 = (n_{1} + n_{2}) / 2 + n_{2} / 2 <= n / 2 + n / 4 = 3 * n / 4</math>. Так как рекурсивные вызовы функции выполняются параллельно, время их выполнения будет равно времени выполнения самого долгого вызова. В худшем случае это <math>T(3 * n / 4)</math>. Тогда сумарное время работы алгоритма слияния будет равно <math>Tmerge(n) = Tmerge(3 * n / 4) + \Theta(\log(n)) = \Theta(\log^2(n))</math>, т.к. асимпототика каждого вызова функции - <math>\Theta(\log(n))</math>, т.е. время, затрачиваемое на бинарный поиск. |
===Сортировка с многопоточным слиянием=== | ===Сортировка с многопоточным слиянием=== | ||
+ | Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы A[leftA \dots rightA] и помещающего отсортированный массив в B[leftB \dots leftB + rightA - leftA] | ||
+ | |||
+ | mergeSortMT2(A, leftA, rightA, B, leftB): | ||
+ | n = r - p + 1 | ||
+ | if n == 1: | ||
+ | B[leftB] = A[leftA] | ||
+ | else | ||
+ | создадим новый массив T[1 \dots 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) | ||
+ | |||
+ | Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов <math>TmergeSort(n) = TmergeSort(n / 2) + Tmerge(n) = TmergeSort(n / 2) + \Theta(\log^2(n)) = \Theta(\log^3(n))</math>. |
Версия 18:14, 18 мая 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)
В данном алгоритме оператор spawn запускает новый поток, а оператор sync ожидает завершения этого потока. Функция merge аналогична функции merge из раздела слияние двух массивов.
Несмотря на наличие двух рекурсивных вызовов, при оценке будем считать, что совершается один вызов, т.к. оба вызова выполняются параллельно с одинаковой асимптотикой. Оценим время работы данного алгоритма: . Данная асимптотика достигается при возможности запускать неограниченное количество потоков независимо друг от друга.
На практике на однопроцессорных компьютерах имеет смысл запускать алгоритм, ограничив количество допустимое количество потоков. Изменим код в соответствии с этими требованиями:
mergeSortMTBounded(array, left, right): mid = (left + right) / 2 if threadCountmaxThreads: threadCount++ spawn mergeSortMTBounded(array, left, mid) else: mergeSortMTBounded(array, left, mid) mergeSortMTBounded(array, mid + 1, right) if threadCount maxThreads: sync threadCount-- merge(array, left, mid, right)
Где threadCount - глобальный счетчик, а maxThreads - ограничение по количеству потоков. Данный алгоритм будет иметь асимптотику:
.Многопоточное слияние
Как видно из оценки первого алгоритма, слияние выполняется слишком долго при том, что существует возможность его ускорить. Рассмотрим алгоритм рекурсивного слияния массивов
и в массив :- Убедимся, что размер больше либо равен размеру
- Вычислим - середину первого массива ( также является и медианой этого массива)
- При помощи бинарного поиска найдем такое, что
- Сольем и в
- Сольем и в
Рассмотрим псевдокод данного алгоритма:
// есливозвращает // если , возвращает // иначе возвращает наибольший индекс из отрезка такой, что binarySearch(x, array, left, right) // слияние и в mergeMT(T, , , , , A, ): = - + 1 = - + 1 if < : swap( , ) swap( , ) swap( , ) if == 0: return else = ( + ) / 2 = binarySearch(T[ , T, , ) = + ( - ) + ( - ) A[ ] = T[ ] spawn mergeMT(T, , - 1, , - 1, A, ) mergeMT(T, + 1, , , , A, + 1) sync
Оценим время выполнения данного алгоритма сверху при возможности запускать неограниченное количество потоков независимо друг от друга. Оба массива содержат
элементов. К моменту рекурсивных вызовов , значит, . В худшем случае один из двух рекурсивных вызовов сольет элементов с элементами и тогда количество элементов первых двух массивов в рекурсивном вызове будет равно . Так как рекурсивные вызовы функции выполняются параллельно, время их выполнения будет равно времени выполнения самого долгого вызова. В худшем случае это . Тогда сумарное время работы алгоритма слияния будет равно , т.к. асимпототика каждого вызова функции - , т.е. время, затрачиваемое на бинарный поиск.Сортировка с многопоточным слиянием
Приведем псевдокод алгоритма, использующего слияние из предыдущего раздела, сортирующего элементы A[leftA \dots rightA] и помещающего отсортированный массив в B[leftB \dots leftB + rightA - leftA]
mergeSortMT2(A, leftA, rightA, B, leftB): n = r - p + 1 if n == 1: B[leftB] = A[leftA] else создадим новый массив T[1 \dots 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)
Оценим данный алгоритм сверху при условии, что возможен запуск неограниченного количества независимых потоков. Из предыдущих пунктов
.