25
Битоническая сортировка Реализация алгоритма с использованием CUDA и MPI

битоническая сортировка

Embed Size (px)

Citation preview

Page 1: битоническая сортировка

Битоническая сортировка

Реализация алгоритма с использованием CUDA и MPI

Page 2: битоническая сортировка

[email protected]

Битоническая сортировка

В основе этой сортировки лежит операция Bn (полуочиститель, half - cleaner) над массивом, параллельно упорядочивающая элементы пар x i и xi + n / 2

Сортировка основана на понятии битонической последовательности и утверждении :

Если набор полуочистителей правильно сортирует произвольную последовательность нулей и единиц, то он корректно сортирует произвольную последовательность. Последовательность [a0, a1, …, an – 1] называется битонической, если она или

состоит из двух монотонных частей (т.е. либо сначала возрастает, а потом убывает, либо наоборот), или получена путем циклического сдвига из такой последовательности. Так, последовательность 5, 7, 6, 4, 2, 1, 3 битоническая, поскольку получена из

1, 3, 5, 7, 6, 4, 2 путем циклического сдвига влево на два элемента.

Page 3: битоническая сортировка

[email protected]

Битоническая сортировка

Доказано, что если применить полуочиститель Bn к битонической последовательности [a0, a1, …, an–1], то получившаяся последовательность обладает следующими свойствами :

1. обе ее половины также будут битоническими.2. любой элемент первой половины будет не больше любого

элемента второй половины.3. хотя бы одна из половин является монотонной.

Page 4: битоническая сортировка

[email protected]

Битоническая сортировка

Применив к битонической последовательности [a0, a1, …, an–1] полуочиститель Bn, получим две последовательности длиной n/2, каждая из которых будет битонической, а каждый элемент первой не превысит каждый элемент второй.

Далее применим к каждой из получившихся половин полуочиститель Bn/2, получим уже четыре битонические последовательности длины n/4.

Применим к каждой из них полуочиститель Bn/2 и продолжим этот процесс до тех пор, пока не придем к n/2 последовательностей из двух элементов.

Применив к каждой из них полуочиститель B2, поскольку все последовательности уже упорядочены, получим отсортированную последовательность.

Page 5: битоническая сортировка

[email protected]

Битоническая сортировка

Итак, последовательное применение полуочистителей Bn, Bn/2, …, B2 сортирует произвольную битоническую последовательность.

Эту операцию называют битоническим слиянием и обозначают Mn.

Page 6: битоническая сортировка

[email protected]

Получение битонической последовательности Пусть задан одномерный неотсортированный массив [a0, a1, …, an–1] Очевидно, что данный массив может быть преобразован к битонической

последовательности с помощью разделения массива на два подмассива и сортировки одной половины по-возрастанию, а другой половины по убыванию

После того, как массив будет преобразован к битонической последовательности к нему можно будет применить операцию битонического слияния Mn и полученный массив станет отсортированным

Для сортировки подмассивов может быть применён алгоритм битонической сортировки, либо любой другой алгоритм сортировки

В данной реализации для сортировки подмассивов применён алгоритм битонической сортировки.

Page 7: битоническая сортировка

[email protected]

Степени двойки

Поскольку в основе алгоритма лежит последовательное применение полуочистителей Bn, Bn/2, …, B2 для массива [a0, a1, …, an–1], то очевидно, что это накладывает ограничение на размер массива к которому может быть применён алгоритм битонической сортировки – размер массива должен быть равен степени двойки, то есть

n=2k

Для сортировки массива произвольного размера N, исходный массив должен быть разделён на подмассивы размера степени двойки. Каждый подмассив сортируется битонической сортировкой, а затем производится слияние уже отсортированных подмассивов в итоговый отсортированный массив

Page 8: битоническая сортировка

[email protected]

Степени двойки

Схема сортировки массива произвольного размера N = 2a+2b+…+2z

Исходный неотсортированный массив

2a 2b 2z

2a 2b 2z

Итоговый отсортированный массив

Page 9: битоническая сортировка

[email protected]

Параметры алгоритма

Для определения порядка сортировки используется функция fn_compare, со следующими свойствами

fn_compare(a,b) < 0 если a < bfn_compare(a,b) > 0 если a > b

fn_compare(a,b) == 0 если a == b И параметр direction = 1, -1 – определяющий порядок сортировки

алгоритмом

Page 10: битоническая сортировка

[email protected]

Ограничения в реализации алгоритма Данная реализация алгоритма реализует сортировку массивов

только из целых чисел, но может быть легко изменена для сортировки массивов требуемого типа данных

Настройки реализации алгоритма для CUDA вынесены в макросы////////////////////////////////////////////////////////////////////////////////////////////// Настроечные аттрибуты// _comparer - функция сравнения двух элементов массива// _indexator - функция определения номера корзины для элемента массива// _non_parallel_sort - фунция сортировки без использования паралельных вычислений// _parallel_sort - фунция сортировки с использованием паралельных вычислений

#define fn_comparer device_comparer<long>#define fn_indexator device_indexator<long>#define fn_non_parallel_sort device_bubble_sort<long>#define fn_parallel_sort host_bucket_sort<long>

Page 11: битоническая сортировка

[email protected]

Разделение на подмассивы размера степени двойки Оптимальное разделение исходного массива на подмассивы

размера степени двойки является темой для отдельной задачи по оптимизации алгоритма, поскольку это разделение может существенно влиять на количество необходимых операций и на время вычислений на параллельных вычислительных устройствах

В данной реализации используется “естественное” разделение исходного массива на подмассивы размера степени двойки – представление размера массива в базисе степеней двойки, то есть если , то размер i-го подмассива ni=ai2i

Очевидно, что в этом случае i-ый подмассив начинается в исходном массиве с индекса (N & ((1<<i)-1)), где & - операция побитового умножения, а << - операция двоичного сдвига числа

Page 12: битоническая сортировка

[email protected]

Шаг алгоритма

Поскольку в данной реализации для получения битонической последовательности из исходного массива применяется сам алгоритм битонической сортировки, то шагом алгоритма является применение полуочистителя ко всему массиву.for(int k=1; (1<<k) <= n ; k++) {

if ( n & (1<<k) ) {

for(int i = 0; i < k ; i++ ) {for( int j = i; j >= 0 ; j-- ) {

// Определим оптимальное разбиения на процессы, нити и циклы // одна нить в просессе будет будет выполнять цикл с указанным количеством итераций

int blocks = 1 << (max(1,(int)k/3));int threads = 1 << (max(1,(int)k/3));int loops = 1 << (k-2*max(1,(int)k/3)-1);

assert((1<<k) == 2*blocks*threads*loops);

// одинаковый шаг в каждом блоке гарантирует отсутствие коллизий (одновременного доступа к одним и тем же данным)global_bitonic_worker<T> <<< blocks, threads >>>(&device_data[n&((1<<k)-1)], n&(1<<k), i, j, loops, direction);}}}

Page 13: битоническая сортировка

[email protected]

Применение полуочистителя

Поскольку мы используем сам битонический алгоритм для получения из исходного массива [a0, a1, …, an – 1], где n=2k, битонической последовательности, то принимаем правило, что левый подмассив мы сортируем в требуемом порядке для итогового массива, а правый – в обратном порядке

Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя полуочиститель B2

Затем подмассивов размера 4 – применяя полуочистители B4,B2 И так далее, до размера 2k – применяя полуочистители Bn, Bn/2, …,

B2

Page 14: битоническая сортировка

[email protected]

Применение полуочистителя

Очевидно, что перед применением полуочистителя мы должны знать в каком порядке мы хотим отсортировать данный подмассив

При применении полуочистетелей B2i, B2

i-1, …, B2 к элементам подмассивов размера 2i мы должны выбрать порядок сортировки основываясь на старших разрядах индекса первого элемента подмассива в исходном массиве

В данной реализации используется чётность старших разрядах начального индекса подмассива начиная с i-ого бита, хотя достаточно использовать только значение i-го бита

int parity = (id >> i);while(parity>1) parity = (parity>>1) ^ (parity&1);

Page 15: битоническая сортировка

[email protected]

Реализация алгоритма с использованием CUDA При программировании для CUDA используется модель общей

памяти, доступной всем параллельным нитям. За один шаг применяется полуочиститель B2

j+1 для всех подмассивов размера 2i, где j < i , i <= k и n=2k

Page 16: битоническая сортировка

[email protected]

Реализация алгоритма с использованием CUDA Основной цикл алгоритма

for(int k=1; (1<<k) <= n ; k++) {

if ( n & (1<<k) ) {

for(int i = 0; i < k ; i++ ) {for( int j = i; j >= 0 ; j-- ) {

// Определим оптимальное разбиения на процессы, нити и циклы // одна нить в просессе будет будет выполнять цикл с указанным количеством итераций

int blocks = 1 << (max(1,(int)k/3));int threads = 1 << (max(1,(int)k/3));int loops = 1 << (k-2*max(1,(int)k/3)-1);

assert((1<<k) == 2*blocks*threads*loops);

// одинаковый шаг в каждом блоке гарантирует отсутствие коллизий (одновременного доступа к одним и тем же данным)global_bitonic_worker<T> <<< blocks, threads >>>(&device_data[n&((1<<k)-1)], n&(1<<k), i, j, loops, direction);}}}

Page 17: битоническая сортировка

[email protected]

Реализация алгоритма с использованием CUDA Алгоритм применения полуочистителя параллельными нитями

// Получаем идентификатор нитиint block = blockDim.x*blockIdx.x + threadIdx.x;int step = 1<<j;for(int y=0; y<loops; y++) {// Получаем идентификатор шага циклаint id = block*loops+y;int offset = ((id>>j)<<(j+1))+(id&((1<<j)-1));int parity = (id >> i);while(parity>1) parity = (parity>>1) ^ (parity&1);parity = 1-(parity<<1); // теперь переменная parity может иметь только 2 значения 1 и -1

assert ((offset+step) < n) ;

int value = parity*direction*fn_comparer(&data[offset],&data[offset+step]);if (value > 0) device_exchange<T>(&data[offset],&data[offset+step],1);}

Page 18: битоническая сортировка

[email protected]

Реализация алгоритма с использованием CUDA Для слияния отсортированных массивов степеней двойки

используется алгоритм слияния отсортированных массивов, реализованный в одной нитиfor(int k=0; k<8*sizeof(int) ; k++ ) size[k] = n & (1<<k);

int total = n;

while(total > 0) {int k = 8*sizeof(int);while( (k-->0) && (size[k] == 0) ) ;for (int i=k; i-- ; ) {if (size[i] > 0 &&direction*fn_comparer(&data[(n&((1<<k)-1))+size[k]-1],&data[(n&((1<<i)-1))+size[i]-1]) < 0){k = i;}}total--;size[k]--;device_copy(&data2[total],&data[(n&((1<<k)-1))+size[k]],1);}

Page 19: битоническая сортировка

[email protected]

Реализация алгоритма с использованием MPI Архитектура MPI предполагает модель независимых вычислительных

устройств, со своей неразделяемой памятью и использованием “хост” процесса, отвечающего за ввод-вывод информации.

Каждый процесс имеет свой уникальный идентификатор, получаемый вызовом метода MPI_Comm_rank

Общее количество процессов может быть получено вызовом метода MPI_Comm_size

В данной реализации все “дочерние” процессы переходят в режим ожидания получения задания от другого процесса, который становится ведущим по отношению к нему.

При окончании работы алгоритма “хост” процесс посылает сигнал об окончании работы всем “дочерним” процессам, после чего “дочерний” процесс завершает свою работу.

Page 20: битоническая сортировка

[email protected]

Реализация алгоритма с использованием MPI Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя

битоническое слияние M2

Затем подмассивов размера 4 – применяя битоническое слияние M4

И так далее, до размера 2k – применяя битоническое слияние Mn

Поскольку применение битонического слияния M2i является применением

полуочистителя B2i к массиву размера 2i, а затем битонического слияния

M2i-1 к левой и правой половинам массивам, то при применении

битонического слияния M2i после применения полуочистителя B2

i к массиву размера 2i текущий процесс может попытаться разделить работу с каким-нибудь другим процессом, поручив ему задание применить битоническое слияние M2

i-1 к правой половине массива, а самому продолжить применять битоническое слияние M2

i-1 к левой половине массива, после чего соединить обратно отсортированные левую и правую части массива.

Page 21: битоническая сортировка

[email protected]

Реализация алгоритма с использованием MPI Для выбора ведомого процесса с которым текущий процесс

может разделить работу при применении битонического слияния M2

i, желательно иметь единый диспечер процессов для оптимальной загрузки всех процессов, но в данной реализации используется следующий алгоритм, организующий все процессы в единое дерево ведущий-ведомый:int child = myrank+shift;if (k>0 && child < nrank) {shift<<=1;/* Отдаём половину массива на обработку процессу с номером child */shift>>=1;} else if (k>0) {/* Обрабатываем всё сами */}

Page 22: битоническая сортировка

[email protected]

Реализация алгоритма с использованием MPI Очевидно, что время работы данной реализации алгоритма

существенно зависит от скорости обмена данными между отдельными процессами

Page 23: битоническая сортировка

[email protected]

Алгоритм битонической сортировки с использованием CUDA

1. Разделить исходный массив размера N на подмассивы степеней двойки в соответствии с представлением N в двоичном базисе

2. Для каждого массива размера 2k выполнить1) Цикл для i от 1 до k

1) Цикл для j от i-1 до 0 с шагом -11) На GPU запускается процедура применения ко всем элементам массива

полуочистителя B2j

2) Конец цикла для j2) Конец цикла для i

3. На GPU запускается процедура слияния всех отсортированных подмассивов размера 2k k=0..31

Page 24: битоническая сортировка

[email protected]

Алгоритм битонической сортировки с использованием MPI1. Хост-процесс формирует исходный массив2. Хост-процесс разделяет исходный массив размера N на подмассивы степеней двойки в соответствии с

представлением N в двоичном базисе3. Для каждого массива размера 2k выполняется

1) Цикл для i от 1 до k1) Разделить массив размера 2k на подмассивы размера 2i 2) Применить ко всем элементам массива размера 2i рекурсивную процедуру битонического слияния М2

i 1) К массиву применить полуочиститель2) Разделить массив на левую и правую части3) Если есть свободный процесс, то

1) Передать правую часть свободному процессу для применения битонического слияния М 2i-1

2) Применить рекурсивную процедуру битонического слияния М2i-1 к левой части

3) Получить обратно правую часть4) Иначе

1) Применить рекурсивную процедуру битонического слияния М2i-1 к левой части

2) Применить рекурсивную процедуру битонического слияния М2i-1 к правой части

3) Конец цикла2) Конец цикла для i

4. Хост-процесс применяет процедуру слияния всех отсортированных подмассивов размера 2k k=0..31

Page 25: битоническая сортировка

[email protected]

Спасибо за внимание

Контакты Дмитрий Протопопов, Москва, Россия

[email protected] +7 916 6969591

Исходные коды и примеры использования доступны по адресу https://github.com/dprotopopov/ParallelSorting