2. Чётно-нечётная сортировка
Для каждой итерации алгоритма операции сравнения-обмена для всех пар
элементов независимы и выполняются одновременно. Рассмотрим случай,
когда число процессоров равно числу элементов, т.е. p=n - число процессоров
(сортируемых элементов). Предположим, что вычислительная система имеет
топологию кольца. Пусть элементы ai (i = 1, .. , n), первоначально
расположены на процессорах pi (i = 1, ... , n). В нечетной итерации каждый
процессор с нечетным номером производит сравнение-обмен своего элемента
с элементом, находящимся на процессоре-соседе справа. Аналогично в
течение четной итерации каждый процессор с четным номером производит
сравнение-обмен своего элемента с элементом правого соседа.
На каждой итерации алгоритма нечетные и четные процессоры выполняют шаг
сравнения-обмена с их правыми соседями за время Q(1). Общее количество
таких итераций – n; поэтому время выполнения параллельной сортировки –
Q(n).
dmitry@protopopov.ru
3. Чётно-нечётная сортировка
Когда число процессоров p меньше числа элементов n, то каждый из
процессов получает свой блок данных n/p и сортирует его за время
Q((n/p)·log(n/p)).
Затем процессоры проходят p итераций (р/2 и чётных, и нечётных) и
делают сравнивания-разбиения:
1. смежные процессоры передают друг другу свои данные, и внутренне их
сортируют (на каждой паре процессоров получаем одинаковые массивы).
2. Затем удвоенный массив делится на 2 части; левый процессор обрабатывает
далее только левую часть (с меньшими значениями данных), а правый – только
правую (с большими значениями данных).
Получаем отсортированный массив после p итераций
dmitry@protopopov.ru
4. Чётно-нечётная сортировка
Когда число процессоров p меньше числа элементов n, когда смежные
процессоры передают друг другу свои данные, и внутренне их сортируют,
то переданные массивы уже являются отсортированными
Поэтому для получения общего отсортированного массива применяется
алгоритм слияния двух отсортированных массивов в один
отсортированный итоговый массив
dmitry@protopopov.ru
6. Параметры алгоритма
Для определения порядка сортировки используется функция 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
7. Ограничения в реализации алгоритма
Данная реализация алгоритма реализует сортировку массивов только из
целых чисел, но может быть легко изменена для сортировки массивов
требуемого типа данных
Настройки реализации алгоритма для 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>
dmitry@protopopov.ru
8. Разделение исходного массива на
подмассивы
При программировании для CUDA используется модель общей памяти,
доступной всем параллельным нитям.
Поэтому для разделения исходного массива на подмассивы создаётся массив
индексов – индекса первого элемента подмассива в исходном массиве
Архитектура MPI предполагает модель независимых вычислительных
устройств, со своей неразделяемой памятью и использованием “хост”
процесса, отвечающего за ввод-вывод информации.
Поэтому “хост” процесс равномерно делит все данные исходного массива
между всеми процессами, включая себя.
dmitry@protopopov.ru
9. Количество подмассивов и их
организация
При программировании для CUDA мы можем определять количество
параллельных нитей, запускаемых на GPU, поэтому создаём чётное
количество подмассивов и рассматриваем вычислительную модель, при
которой блоки объединены циклически, то есть левым блоком для
первого блока является последний блок
При программировании для MPI мы не можем контролировать количество
запущенных процессов, поэтому рассматриваем вычислительную модель,
при которой блоки объединены последовательно, то есть первый блок не
имеет левого, а последний - правого
dmitry@protopopov.ru
10. Сортировка подмассивов
В данной реализации для CUDA каждый подмассив сортируется отдельной
нитью с помощью реализованного алгоритма пузырьковой сортировки
В данной реализации для MPI каждый подмассив сортируется
соответствующим процессом вызовом библиотечной функции qsort,
реализующей алгоритм быстрой сортировки
dmitry@protopopov.ru
11. Шаг алгоритма
При программировании для CUDA используется модель общей памяти,
доступной всем параллельным нитям.
Поэтому для каждой пары подмассивов производится сортировка слияниями с
копированием результата в новый массив, при этом на GPU запускается
количество параллельных нитей равное числу пар подмассивов
Архитектура MPI предполагает модель независимых вычислительных
устройств, со своей неразделяемой памятью.
Поэтому каждый процесс определяет своего соседа исходя из своего номера
процесса, полученного вызовом метода MPI_Comm_rank
производит взаимный обмен данными подмассива с соседом
производит сортировку слияниями двух уже отсортированных массивов и
оставляет у себя нужную часть
dmitry@protopopov.ru
12. Соединение подмассивов в итоговый
массив
При программировании для CUDA используется модель общей памяти,
доступной всем параллельным нитям и разделение исходного массива
производилось только с помощью создания вспомогательной таблицы –
индексов первого элемента подмассива в исходном массиве
Поэтому итоговый массив уже соединён в результате выхода последнего шага
алгоритма
Архитектура MPI предполагает модель независимых вычислительных
устройств, со своей неразделяемой памятью.
Поэтому итоговый массив формируется “хост” процессом в результате
последовательного опроса “дочерних” процессов
dmitry@protopopov.ru
13. Возможности оптимизации реализации
алгоритма
В реализации для MPI, на этапе слияния двух отсортированных
подмассивов в один отсортированный массив, нет необходимости
производить формирование массива полного размера, равного сумме
размеров двух подмассивов. Достаточно сформировать только
отсортированную оставляемую часть, что позволит сократить количество
необходимых вычислительных операций до 2-х раз, хотя в среднем
сокращение количества вычислительных операций будет значительно
меньше чем 2 раза.
dmitry@protopopov.ru
14. Алгоритм чётно-нечётной сортировки
с использованием MPI
1. Хост-процесс формирует исходный массив
2. Хост-процесс равномерно распределяет элементы исходного массива между всеми P
процессами, включая сам хост-процесс
3. Все процессы сортируют полученные подмассивы процедурой qsort
4. Каждый процесс p задаёт в качестве соседа для чётного шага процесс p-n, а в качестве
соседа для нечётного шага процесс p+n, где n=(-1)p
5. Цикл для i от 0 до P-1
1. Каждый процесс t обменивается подмассивом с соседом, определяемым чётностью I
2. Каждый процесс t применяет процедуру слияния отсортированных подмассивов
3. Каждый процесс t оставляет у себя левую или правую половину отсортированного массива,
в зависимости, с соседом слева или справа был произведён обмен подмассивами
6. Конец цикла для i
7. Хост-процесс последовательно опрашивает процессы и присоединяет полученные
подмассивы к результирующему массиву
dmitry@protopopov.ru
15. Алгоритм чётно-нечётной сортировки
с использованием CUDA
1. Исходный массив размера N загружается в память GPU
2. В памяти GPU создаётся массив INDEX из 2*K+1 индексов элементов
исходного массива, где INDEX[0]=0 и INDEX[2*K]=N, а остальные элементы
равномерно распределены по возрастанию между 0 и N
3. На GPU запускается 2*K параллельных процесса, каждый из которых
сортирует один подмассив между индексами INDEX[t]-INDEX[t+1] методом
пузырьковой сортировки
4. Цикл для i от 0 до 2*K-1
1. На GPU запускается K параллельных процесса, каждый из который применяет
процедуру слияния отсортированных массивов к массиву между индексами
INDEX[2*t+p]-INDEX[2*t+1+p mod 2*K] и к массиву между индексами
INDEX[2*t+1+p mod 2*K]-INDEX[2*t+2+p mod 2*K]
5. Конец цикла для i
dmitry@protopopov.ru
16. Спасибо за внимание
Контакты
Дмитрий Протопопов, Москва, Россия
dmitry@protopopov.ru
+7 916 6969591
Исходные коды и примеры использования доступны по адресу
https://github.com/dprotopopov/ParallelSorting
dmitry@protopopov.ru