SlideShare a Scribd company logo
1 of 25
Битоническая
сортировка
Реализация алгоритма с использованием CUDA и MPI
Битоническая сортировка
 В основе этой сортировки лежит операция Bn (полуочиститель, half - cleaner)
над массивом, параллельно упорядочивающая элементы пар xi и xi + n / 2
 Сортировка основана на понятии битонической последовательности и
утверждении :
Если набор полуочистителей правильно сортирует произвольную
последовательность нулей и единиц, то он корректно сортирует
произвольную последовательность.
 Последовательность [a0, a1, …, an – 1] называется битонической, если она или
состоит из двух монотонных частей (т.е. либо сначала возрастает, а потом
убывает, либо наоборот), или получена путем циклического сдвига из такой
последовательности.
 Так, последовательность 5, 7, 6, 4, 2, 1, 3 битоническая, поскольку получена из 1, 3,
5, 7, 6, 4, 2 путем циклического сдвига влево на два элемента.
dmitry@protopopov.ru
Битоническая сортировка
 Доказано, что если применить полуочиститель Bn к битонической
последовательности [a0, a1, …, an–1], то получившаяся последовательность
обладает следующими свойствами :
1. обе ее половины также будут битоническими.
2. любой элемент первой половины будет не больше любого элемента
второй половины.
3. хотя бы одна из половин является монотонной.
dmitry@protopopov.ru
Битоническая сортировка
 Применив к битонической последовательности [a0, a1, …, an–1]
полуочиститель Bn, получим две последовательности длиной n/2, каждая
из которых будет битонической, а каждый элемент первой не превысит
каждый элемент второй.
 Далее применим к каждой из получившихся половин полуочиститель Bn/2,
получим уже четыре битонические последовательности длины n/4.
 Применим к каждой из них полуочиститель Bn/2 и продолжим этот процесс
до тех пор, пока не придем к n/2 последовательностей из двух элементов.
 Применив к каждой из них полуочиститель B2, поскольку все
последовательности уже упорядочены, получим отсортированную
последовательность.
dmitry@protopopov.ru
Битоническая сортировка
 Итак, последовательное применение полуочистителей Bn, Bn/2, …, B2
сортирует произвольную битоническую последовательность.
 Эту операцию называют битоническим слиянием и обозначают Mn.
dmitry@protopopov.ru
Получение битонической
последовательности
 Пусть задан одномерный неотсортированный массив [a0, a1, …, an–1]
 Очевидно, что данный массив может быть преобразован к битонической
последовательности с помощью разделения массива на два подмассива и
сортировки одной половины по-возрастанию, а другой половины по
убыванию
 После того, как массив будет преобразован к битонической
последовательности к нему можно будет применить операцию
битонического слияния Mn и полученный массив станет отсортированным
 Для сортировки подмассивов может быть применён алгоритм
битонической сортировки, либо любой другой алгоритм сортировки
 В данной реализации для сортировки подмассивов применён алгоритм
битонической сортировки.
dmitry@protopopov.ru
Степени двойки
 Поскольку в основе алгоритма лежит последовательное применение
полуочистителей Bn, Bn/2, …, B2 для массива [a0, a1, …, an–1], то очевидно,
что это накладывает ограничение на размер массива к которому может
быть применён алгоритм битонической сортировки – размер массива
должен быть равен степени двойки, то есть
n=2k
 Для сортировки массива произвольного размера N, исходный массив
должен быть разделён на подмассивы размера степени двойки. Каждый
подмассив сортируется битонической сортировкой, а затем производится
слияние уже отсортированных подмассивов в итоговый отсортированный
массив
dmitry@protopopov.ru
Степени двойки
 Схема сортировки массива произвольного размера N = 2a+2b+…+2z
dmitry@protopopov.ru
Исходный неотсортированный массив
2a 2b
2z
2a 2b 2z
Итоговый отсортированный массив
Параметры алгоритма
 Для определения порядка сортировки используется функция fn_compare,
со следующими свойствами
fn_compare(a,b) < 0 если a < b
fn_compare(a,b) > 0 если a > b
fn_compare(a,b) == 0 если a == b
 И параметр direction = 1, -1 – определяющий порядок сортировки
алгоритмом
dmitry@protopopov.ru
Ограничения в реализации алгоритма
 Данная реализация алгоритма реализует сортировку массивов только из
целых чисел, но может быть легко изменена для сортировки массивов
требуемого типа данных
 Настройки реализации алгоритма для CUDA вынесены в макросы
dmitry@protopopov.ru
///////////////////////////////////////////////////////////////////////////////////////
/////
// Настроечные аттрибуты
// _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>
Разделение на подмассивы размера
степени двойки
 Оптимальное разделение исходного массива на подмассивы размера
степени двойки является темой для отдельной задачи по оптимизации
алгоритма, поскольку это разделение может существенно влиять на
количество необходимых операций и на время вычислений на
параллельных вычислительных устройствах
 В данной реализации используется “естественное” разделение исходного
массива на подмассивы размера степени двойки – представление размера
массива в базисе степеней двойки, то есть если 𝑁 = 𝑎𝑖2𝑖
, то размер i-го
подмассива ni=ai2i
 Очевидно, что в этом случае i-ый подмассив начинается в исходном
массиве с индекса (N & ((1<<i)-1)), где & - операция побитового
умножения, а << - операция двоичного сдвига числа
dmitry@protopopov.ru
Шаг алгоритма
 Поскольку в данной реализации для получения битонической
последовательности из исходного массива применяется сам алгоритм
битонической сортировки, то шагом алгоритма является применение
полуочистителя ко всему массиву.
dmitry@protopopov.ru
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);
}}}
Применение полуочистителя
 Поскольку мы используем сам битонический алгоритм для получения из
исходного массива [a0, a1, …, an – 1], где n=2k, битонической
последовательности, то принимаем правило, что левый подмассив мы
сортируем в требуемом порядке для итогового массива, а правый – в
обратном порядке
 Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя
полуочиститель B2
 Затем подмассивов размера 4 – применяя полуочистители B4,B2
 И так далее, до размера 2k – применяя полуочистители Bn, Bn/2, …, B2
dmitry@protopopov.ru
Применение полуочистителя
 Очевидно, что перед применением полуочистителя мы должны знать в
каком порядке мы хотим отсортировать данный подмассив
 При применении полуочистетелей B2
i, B2
i-1, …, B2 к элементам подмассивов
размера 2i мы должны выбрать порядок сортировки основываясь на
старших разрядах индекса первого элемента подмассива в исходном
массиве
 В данной реализации используется чётность старших разрядах начального
индекса подмассива начиная с i-ого бита, хотя достаточно использовать
только значение i-го бита
int parity = (id >> i);
while(parity>1) parity = (parity>>1) ^ (parity&1);
dmitry@protopopov.ru
Реализация алгоритма с
использованием CUDA
 При программировании для CUDA используется модель общей памяти,
доступной всем параллельным нитям.
 За один шаг применяется полуочиститель B2
j+1 для всех подмассивов
размера 2i, где j < i , i <= k и n=2k
dmitry@protopopov.ru
Реализация алгоритма с
использованием CUDA
 Основной цикл алгоритма
dmitry@protopopov.ru
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);
}}}
Реализация алгоритма с
использованием CUDA
 Алгоритм применения полуочистителя параллельными нитями
dmitry@protopopov.ru
// Получаем идентификатор нити
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);
}
Реализация алгоритма с
использованием CUDA
 Для слияния отсортированных массивов степеней двойки используется
алгоритм слияния отсортированных массивов, реализованный в одной
нити
dmitry@protopopov.ru
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);
}
Реализация алгоритма с
использованием MPI
 Архитектура MPI предполагает модель независимых вычислительных
устройств, со своей неразделяемой памятью и использованием “хост”
процесса, отвечающего за ввод-вывод информации.
 Каждый процесс имеет свой уникальный идентификатор, получаемый
вызовом метода MPI_Comm_rank
 Общее количество процессов может быть получено вызовом метода
MPI_Comm_size
 В данной реализации все “дочерние” процессы переходят в режим
ожидания получения задания от другого процесса, который становится
ведущим по отношению к нему.
 При окончании работы алгоритма “хост” процесс посылает сигнал об
окончании работы всем “дочерним” процессам, после чего “дочерний”
процесс завершает свою работу.
dmitry@protopopov.ru
Реализация алгоритма с
использованием MPI
 Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя
битоническое слияние M2
 Затем подмассивов размера 4 – применяя битоническое слияние M4
 И так далее, до размера 2k – применяя битоническое слияние Mn
 Поскольку применение битонического слияния M2
i является применением
полуочистителя B2
i к массиву размера 2i, а затем битонического слияния M2
i-1 к
левой и правой половинам массивам, то при применении битонического
слияния M2
i после применения полуочистителя B2
i к массиву размера 2i
текущий процесс может попытаться разделить работу с каким-нибудь другим
процессом, поручив ему задание применить битоническое слияние M2
i-1 к
правой половине массива, а самому продолжить применять битоническое
слияние M2
i-1 к левой половине массива, после чего соединить обратно
отсортированные левую и правую части массива.
dmitry@protopopov.ru
Реализация алгоритма с
использованием MPI
 Для выбора ведомого процесса с которым текущий процесс может
разделить работу при применении битонического слияния M2
i, желательно
иметь единый диспечер процессов для оптимальной загрузки всех
процессов, но в данной реализации используется следующий алгоритм,
организующий все процессы в единое дерево ведущий-ведомый:
dmitry@protopopov.ru
int child = myrank+shift;
if (k>0 && child < nrank) {
shift<<=1;
/* Отдаём половину массива на обработку процессу с номером child */
shift>>=1;
} else if (k>0) {
/* Обрабатываем всё сами */
}
Реализация алгоритма с
использованием MPI
 Очевидно, что время работы данной реализации алгоритма существенно
зависит от скорости обмена данными между отдельными процессами
dmitry@protopopov.ru
Алгоритм битонической сортировки с
использованием CUDA
1. Разделить исходный массив размера N на подмассивы степеней двойки в
соответствии с представлением N в двоичном базисе
2. Для каждого массива размера 2k выполнить
1) Цикл для i от 1 до k
1) Цикл для j от i-1 до 0 с шагом -1
1) На GPU запускается процедура применения ко всем элементам массива полуочистителя B2
j
2) Конец цикла для j
2) Конец цикла для i
3. На GPU запускается процедура слияния всех отсортированных
подмассивов размера 2k k=0..31
dmitry@protopopov.ru
Алгоритм битонической сортировки с
использованием MPI
1. Хост-процесс формирует исходный массив
2. Хост-процесс разделяет исходный массив размера N на подмассивы степеней двойки в соответствии с представлением N в двоичном базисе
3. Для каждого массива размера 2k выполняется
1) Цикл для i от 1 до k
1) Разделить массив размера 2k на подмассивы размера 2i
2) Применить ко всем элементам массива размера 2i рекурсивную процедуру битонического слияния М2
i
1) К массиву применить полуочиститель
2) Разделить массив на левую и правую части
3) Если есть свободный процесс, то
1) Передать правую часть свободному процессу для применения битонического слияния М2
i-1
2) Применить рекурсивную процедуру битонического слияния М2
i-1 к левой части
3) Получить обратно правую часть
4) Иначе
1) Применить рекурсивную процедуру битонического слияния М2
i-1 к левой части
2) Применить рекурсивную процедуру битонического слияния М2
i-1 к правой части
3) Конец цикла
2) Конец цикла для i
4. Хост-процесс применяет процедуру слияния всех отсортированных подмассивов размера 2k k=0..31
dmitry@protopopov.ru
Спасибо за внимание
 Контакты
 Дмитрий Протопопов, Москва, Россия
dmitry@protopopov.ru
+7 916 6969591
Исходные коды и примеры использования доступны по адресу
https://github.com/dprotopopov/ParallelSorting
dmitry@protopopov.ru

More Related Content

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

Алгоритмы и структуры данных весна 2014 лекция 3
Алгоритмы и структуры данных весна 2014 лекция 3Алгоритмы и структуры данных весна 2014 лекция 3
Алгоритмы и структуры данных весна 2014 лекция 3
Technopark
 
Лекция 10: Графы. Остовные деревья минимальной стоимости
Лекция 10: Графы. Остовные деревья минимальной стоимостиЛекция 10: Графы. Остовные деревья минимальной стоимости
Лекция 10: Графы. Остовные деревья минимальной стоимости
Mikhail Kurnosov
 
Моделирование|Обучение
Моделирование|ОбучениеМоделирование|Обучение
Моделирование|Обучение
funkypublic
 
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
Mikhail Kurnosov
 

Similar to битоническая сортировка (20)

Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)Лекция 7. Стандарт OpenMP (подолжение)
Лекция 7. Стандарт OpenMP (подолжение)
 
РЕАЛИЗАЦИЯ РАДИАЛЬНО-БАЗИСНОЙ НЕЙРОННОЙ СЕТИ НА МАССИВНО-ПАРАЛЛЕЛЬНОЙ АРХИТЕК...
РЕАЛИЗАЦИЯ РАДИАЛЬНО-БАЗИСНОЙ НЕЙРОННОЙ СЕТИ НА МАССИВНО-ПАРАЛЛЕЛЬНОЙ АРХИТЕК...РЕАЛИЗАЦИЯ РАДИАЛЬНО-БАЗИСНОЙ НЕЙРОННОЙ СЕТИ НА МАССИВНО-ПАРАЛЛЕЛЬНОЙ АРХИТЕК...
РЕАЛИЗАЦИЯ РАДИАЛЬНО-БАЗИСНОЙ НЕЙРОННОЙ СЕТИ НА МАССИВНО-ПАРАЛЛЕЛЬНОЙ АРХИТЕК...
 
Алгоритмы и структуры данных весна 2014 лекция 3
Алгоритмы и структуры данных весна 2014 лекция 3Алгоритмы и структуры данных весна 2014 лекция 3
Алгоритмы и структуры данных весна 2014 лекция 3
 
Работа с БД в Java
Работа с БД в JavaРабота с БД в Java
Работа с БД в Java
 
Лекция 10: Графы. Остовные деревья минимальной стоимости
Лекция 10: Графы. Остовные деревья минимальной стоимостиЛекция 10: Графы. Остовные деревья минимальной стоимости
Лекция 10: Графы. Остовные деревья минимальной стоимости
 
16
1616
16
 
!Predictive analytics part_2
!Predictive analytics part_2!Predictive analytics part_2
!Predictive analytics part_2
 
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
Быстрые конструкции в Python - Олег Шидловский, Python Meetup 26.09.2014
 
Mod Film
Mod FilmMod Film
Mod Film
 
Моделирование|Обучение
Моделирование|ОбучениеМоделирование|Обучение
Моделирование|Обучение
 
Java 8 puzzlers
Java 8 puzzlersJava 8 puzzlers
Java 8 puzzlers
 
Алгоритмы сортировки
Алгоритмы сортировкиАлгоритмы сортировки
Алгоритмы сортировки
 
основы Java переменные, циклы
основы Java   переменные, циклыосновы Java   переменные, циклы
основы Java переменные, циклы
 
МЕТОД ПРЕДСТАВЛЕНИЯ АВТОМАТОВ ЛИНЕЙНЫМИ БИНАРНЫМИ ГРАФАМИ ДЛЯ ИСПОЛЬЗОВАНИЯ В...
МЕТОД ПРЕДСТАВЛЕНИЯ АВТОМАТОВ ЛИНЕЙНЫМИ БИНАРНЫМИ ГРАФАМИ ДЛЯ ИСПОЛЬЗОВАНИЯ В...МЕТОД ПРЕДСТАВЛЕНИЯ АВТОМАТОВ ЛИНЕЙНЫМИ БИНАРНЫМИ ГРАФАМИ ДЛЯ ИСПОЛЬЗОВАНИЯ В...
МЕТОД ПРЕДСТАВЛЕНИЯ АВТОМАТОВ ЛИНЕЙНЫМИ БИНАРНЫМИ ГРАФАМИ ДЛЯ ИСПОЛЬЗОВАНИЯ В...
 
Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)Семинар 3. Многопоточное программирование на OpenMP (часть 3)
Семинар 3. Многопоточное программирование на OpenMP (часть 3)
 
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
Лекция 6. Параллельная сортировка. Алгоритмы комбинаторного поиска. Параллель...
 
Лекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building BlocksЛекция 8. Intel Threading Building Blocks
Лекция 8. Intel Threading Building Blocks
 
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
Cтруктурно-ориентированные алгоритмы коллективных обменов в распределенных вы...
 
Лекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимостиЛекция 10. Графы. Остовные деревья минимальной стоимости
Лекция 10. Графы. Остовные деревья минимальной стоимости
 
Алгоритмы решения задачи о булевой выполнимости (SAT) и их применение в крипт...
Алгоритмы решения задачи о булевой выполнимости (SAT) и их применение в крипт...Алгоритмы решения задачи о булевой выполнимости (SAT) и их применение в крипт...
Алгоритмы решения задачи о булевой выполнимости (SAT) и их применение в крипт...
 

More from Dmitry Protopopov

Обработка приватных данных на публичных вычислительных сетях
Обработка приватных данных на публичных вычислительных сетяхОбработка приватных данных на публичных вычислительных сетях
Обработка приватных данных на публичных вычислительных сетях
Dmitry Protopopov
 
фурье вычисления для сравнения изображений
фурье вычисления для сравнения изображенийфурье вычисления для сравнения изображений
фурье вычисления для сравнения изображений
Dmitry Protopopov
 
метод широкополосного сигнала
метод широкополосного сигналаметод широкополосного сигнала
метод широкополосного сигнала
Dmitry Protopopov
 

More from Dmitry Protopopov (6)

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

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

  • 2. Битоническая сортировка  В основе этой сортировки лежит операция Bn (полуочиститель, half - cleaner) над массивом, параллельно упорядочивающая элементы пар xi и xi + n / 2  Сортировка основана на понятии битонической последовательности и утверждении : Если набор полуочистителей правильно сортирует произвольную последовательность нулей и единиц, то он корректно сортирует произвольную последовательность.  Последовательность [a0, a1, …, an – 1] называется битонической, если она или состоит из двух монотонных частей (т.е. либо сначала возрастает, а потом убывает, либо наоборот), или получена путем циклического сдвига из такой последовательности.  Так, последовательность 5, 7, 6, 4, 2, 1, 3 битоническая, поскольку получена из 1, 3, 5, 7, 6, 4, 2 путем циклического сдвига влево на два элемента. dmitry@protopopov.ru
  • 3. Битоническая сортировка  Доказано, что если применить полуочиститель Bn к битонической последовательности [a0, a1, …, an–1], то получившаяся последовательность обладает следующими свойствами : 1. обе ее половины также будут битоническими. 2. любой элемент первой половины будет не больше любого элемента второй половины. 3. хотя бы одна из половин является монотонной. dmitry@protopopov.ru
  • 4. Битоническая сортировка  Применив к битонической последовательности [a0, a1, …, an–1] полуочиститель Bn, получим две последовательности длиной n/2, каждая из которых будет битонической, а каждый элемент первой не превысит каждый элемент второй.  Далее применим к каждой из получившихся половин полуочиститель Bn/2, получим уже четыре битонические последовательности длины n/4.  Применим к каждой из них полуочиститель Bn/2 и продолжим этот процесс до тех пор, пока не придем к n/2 последовательностей из двух элементов.  Применив к каждой из них полуочиститель B2, поскольку все последовательности уже упорядочены, получим отсортированную последовательность. dmitry@protopopov.ru
  • 5. Битоническая сортировка  Итак, последовательное применение полуочистителей Bn, Bn/2, …, B2 сортирует произвольную битоническую последовательность.  Эту операцию называют битоническим слиянием и обозначают Mn. dmitry@protopopov.ru
  • 6. Получение битонической последовательности  Пусть задан одномерный неотсортированный массив [a0, a1, …, an–1]  Очевидно, что данный массив может быть преобразован к битонической последовательности с помощью разделения массива на два подмассива и сортировки одной половины по-возрастанию, а другой половины по убыванию  После того, как массив будет преобразован к битонической последовательности к нему можно будет применить операцию битонического слияния Mn и полученный массив станет отсортированным  Для сортировки подмассивов может быть применён алгоритм битонической сортировки, либо любой другой алгоритм сортировки  В данной реализации для сортировки подмассивов применён алгоритм битонической сортировки. dmitry@protopopov.ru
  • 7. Степени двойки  Поскольку в основе алгоритма лежит последовательное применение полуочистителей Bn, Bn/2, …, B2 для массива [a0, a1, …, an–1], то очевидно, что это накладывает ограничение на размер массива к которому может быть применён алгоритм битонической сортировки – размер массива должен быть равен степени двойки, то есть n=2k  Для сортировки массива произвольного размера N, исходный массив должен быть разделён на подмассивы размера степени двойки. Каждый подмассив сортируется битонической сортировкой, а затем производится слияние уже отсортированных подмассивов в итоговый отсортированный массив dmitry@protopopov.ru
  • 8. Степени двойки  Схема сортировки массива произвольного размера N = 2a+2b+…+2z dmitry@protopopov.ru Исходный неотсортированный массив 2a 2b 2z 2a 2b 2z Итоговый отсортированный массив
  • 9. Параметры алгоритма  Для определения порядка сортировки используется функция fn_compare, со следующими свойствами fn_compare(a,b) < 0 если a < b fn_compare(a,b) > 0 если a > b fn_compare(a,b) == 0 если a == b  И параметр direction = 1, -1 – определяющий порядок сортировки алгоритмом dmitry@protopopov.ru
  • 10. Ограничения в реализации алгоритма  Данная реализация алгоритма реализует сортировку массивов только из целых чисел, но может быть легко изменена для сортировки массивов требуемого типа данных  Настройки реализации алгоритма для CUDA вынесены в макросы dmitry@protopopov.ru /////////////////////////////////////////////////////////////////////////////////////// ///// // Настроечные аттрибуты // _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>
  • 11. Разделение на подмассивы размера степени двойки  Оптимальное разделение исходного массива на подмассивы размера степени двойки является темой для отдельной задачи по оптимизации алгоритма, поскольку это разделение может существенно влиять на количество необходимых операций и на время вычислений на параллельных вычислительных устройствах  В данной реализации используется “естественное” разделение исходного массива на подмассивы размера степени двойки – представление размера массива в базисе степеней двойки, то есть если 𝑁 = 𝑎𝑖2𝑖 , то размер i-го подмассива ni=ai2i  Очевидно, что в этом случае i-ый подмассив начинается в исходном массиве с индекса (N & ((1<<i)-1)), где & - операция побитового умножения, а << - операция двоичного сдвига числа dmitry@protopopov.ru
  • 12. Шаг алгоритма  Поскольку в данной реализации для получения битонической последовательности из исходного массива применяется сам алгоритм битонической сортировки, то шагом алгоритма является применение полуочистителя ко всему массиву. dmitry@protopopov.ru 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); }}}
  • 13. Применение полуочистителя  Поскольку мы используем сам битонический алгоритм для получения из исходного массива [a0, a1, …, an – 1], где n=2k, битонической последовательности, то принимаем правило, что левый подмассив мы сортируем в требуемом порядке для итогового массива, а правый – в обратном порядке  Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя полуочиститель B2  Затем подмассивов размера 4 – применяя полуочистители B4,B2  И так далее, до размера 2k – применяя полуочистители Bn, Bn/2, …, B2 dmitry@protopopov.ru
  • 14. Применение полуочистителя  Очевидно, что перед применением полуочистителя мы должны знать в каком порядке мы хотим отсортировать данный подмассив  При применении полуочистетелей B2 i, B2 i-1, …, B2 к элементам подмассивов размера 2i мы должны выбрать порядок сортировки основываясь на старших разрядах индекса первого элемента подмассива в исходном массиве  В данной реализации используется чётность старших разрядах начального индекса подмассива начиная с i-ого бита, хотя достаточно использовать только значение i-го бита int parity = (id >> i); while(parity>1) parity = (parity>>1) ^ (parity&1); dmitry@protopopov.ru
  • 15. Реализация алгоритма с использованием CUDA  При программировании для CUDA используется модель общей памяти, доступной всем параллельным нитям.  За один шаг применяется полуочиститель B2 j+1 для всех подмассивов размера 2i, где j < i , i <= k и n=2k dmitry@protopopov.ru
  • 16. Реализация алгоритма с использованием CUDA  Основной цикл алгоритма dmitry@protopopov.ru 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); }}}
  • 17. Реализация алгоритма с использованием CUDA  Алгоритм применения полуочистителя параллельными нитями dmitry@protopopov.ru // Получаем идентификатор нити 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); }
  • 18. Реализация алгоритма с использованием CUDA  Для слияния отсортированных массивов степеней двойки используется алгоритм слияния отсортированных массивов, реализованный в одной нити dmitry@protopopov.ru 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); }
  • 19. Реализация алгоритма с использованием MPI  Архитектура MPI предполагает модель независимых вычислительных устройств, со своей неразделяемой памятью и использованием “хост” процесса, отвечающего за ввод-вывод информации.  Каждый процесс имеет свой уникальный идентификатор, получаемый вызовом метода MPI_Comm_rank  Общее количество процессов может быть получено вызовом метода MPI_Comm_size  В данной реализации все “дочерние” процессы переходят в режим ожидания получения задания от другого процесса, который становится ведущим по отношению к нему.  При окончании работы алгоритма “хост” процесс посылает сигнал об окончании работы всем “дочерним” процессам, после чего “дочерний” процесс завершает свою работу. dmitry@protopopov.ru
  • 20. Реализация алгоритма с использованием MPI  Алгоритм мы начинаем с сортировки подмассивов размера 2 – применяя битоническое слияние M2  Затем подмассивов размера 4 – применяя битоническое слияние M4  И так далее, до размера 2k – применяя битоническое слияние Mn  Поскольку применение битонического слияния M2 i является применением полуочистителя B2 i к массиву размера 2i, а затем битонического слияния M2 i-1 к левой и правой половинам массивам, то при применении битонического слияния M2 i после применения полуочистителя B2 i к массиву размера 2i текущий процесс может попытаться разделить работу с каким-нибудь другим процессом, поручив ему задание применить битоническое слияние M2 i-1 к правой половине массива, а самому продолжить применять битоническое слияние M2 i-1 к левой половине массива, после чего соединить обратно отсортированные левую и правую части массива. dmitry@protopopov.ru
  • 21. Реализация алгоритма с использованием MPI  Для выбора ведомого процесса с которым текущий процесс может разделить работу при применении битонического слияния M2 i, желательно иметь единый диспечер процессов для оптимальной загрузки всех процессов, но в данной реализации используется следующий алгоритм, организующий все процессы в единое дерево ведущий-ведомый: dmitry@protopopov.ru int child = myrank+shift; if (k>0 && child < nrank) { shift<<=1; /* Отдаём половину массива на обработку процессу с номером child */ shift>>=1; } else if (k>0) { /* Обрабатываем всё сами */ }
  • 22. Реализация алгоритма с использованием MPI  Очевидно, что время работы данной реализации алгоритма существенно зависит от скорости обмена данными между отдельными процессами dmitry@protopopov.ru
  • 23. Алгоритм битонической сортировки с использованием CUDA 1. Разделить исходный массив размера N на подмассивы степеней двойки в соответствии с представлением N в двоичном базисе 2. Для каждого массива размера 2k выполнить 1) Цикл для i от 1 до k 1) Цикл для j от i-1 до 0 с шагом -1 1) На GPU запускается процедура применения ко всем элементам массива полуочистителя B2 j 2) Конец цикла для j 2) Конец цикла для i 3. На GPU запускается процедура слияния всех отсортированных подмассивов размера 2k k=0..31 dmitry@protopopov.ru
  • 24. Алгоритм битонической сортировки с использованием MPI 1. Хост-процесс формирует исходный массив 2. Хост-процесс разделяет исходный массив размера N на подмассивы степеней двойки в соответствии с представлением N в двоичном базисе 3. Для каждого массива размера 2k выполняется 1) Цикл для i от 1 до k 1) Разделить массив размера 2k на подмассивы размера 2i 2) Применить ко всем элементам массива размера 2i рекурсивную процедуру битонического слияния М2 i 1) К массиву применить полуочиститель 2) Разделить массив на левую и правую части 3) Если есть свободный процесс, то 1) Передать правую часть свободному процессу для применения битонического слияния М2 i-1 2) Применить рекурсивную процедуру битонического слияния М2 i-1 к левой части 3) Получить обратно правую часть 4) Иначе 1) Применить рекурсивную процедуру битонического слияния М2 i-1 к левой части 2) Применить рекурсивную процедуру битонического слияния М2 i-1 к правой части 3) Конец цикла 2) Конец цикла для i 4. Хост-процесс применяет процедуру слияния всех отсортированных подмассивов размера 2k k=0..31 dmitry@protopopov.ru
  • 25. Спасибо за внимание  Контакты  Дмитрий Протопопов, Москва, Россия dmitry@protopopov.ru +7 916 6969591 Исходные коды и примеры использования доступны по адресу https://github.com/dprotopopov/ParallelSorting dmitry@protopopov.ru