Резюме. Мы обсуждаем здесь наилучшие способы оптимизации сортировки больших массивов составных объектов по нечисловым ключам. Также рассматривается способ уменьшения количества выполняемых операций (сложность) имеющихся алгоритмов сортировки. Конкретный базовый алгоритм сортировки выбирается разработчиком по своему усмотрению (см. условие 1 в замечаниях).

Введение: Проблематикой имплементации паралелльной сортировки занимаются разные специалисты по всему миру. Подавляющее число специалистов рассматривает платформы GPU и их суперскалярность как эффективную (в вычислительном плане) базу для разработки новых алгоритмов. На Хабре исследованиями в этой области занимались [KS1], [KILY1], [PatZ1], [Ms1].

Стоит отметить несколько минусов выбранного авторами похода:

  • объем обрабатываемого массива жестко ограничен возможностями оборудования

  • в исследованиях уделяется вопрос сортировки исключительно простых типов данных: натуральных чисел; вопрос сортировки более сложных и составных типов не рассматривается

  • при сортировке на GPU отсутствует возможность эффективной работы с указателями; как следствие сложно обеспечить сортировку составных объектов, по не числовым/составным ключам, особенно, когда сортируемые объекты хранятся во внешней памяти

  • проблема останова (трудно в концепции SIMD сформировать условие завершения работы алгоритма)

Автор полагает, что приведенные недостатки при сортировке на GPU и подобных суперскалярных платформах (использование концепции SIMD вообще), являются фундаментально неустранимыми.

Проведенные автором методики полностью лишены указанных недостатков, более  ­̶  позволяют разработчику выбирать базовый алгоритм сортировки в соответствии с требованиями к результату (устойчивость, память, ..), однако требует соблюдения следующих условий:

Условие 1: К описанной далее оптимизации склонны только алгоритмы со сложностью в худшем и среднем случаях O(NlogN)

Условие 2: Сортируемые объекты должны быть способны к группировке (кластеризации) по своим ключам[1]

Условие 3: Объекты одного класса (элементы одной группы), должны быть строго меньше/больше объектов других классов.

[1] Например сортируемыми объектами могут выступать записи вида {name: 'latin_string', age: number, born: date}, хранимые на жестком диске. Здесь сортируемый ключ: поле 'name' объектов - английская строка неопределенной длины

Условие3 позволяет упорядочивать группы объектов между собой.

Как показали исследования условиям 2 и 3 соответствуют не только объекты с числовыми ключами, но и со строковыми (текстовыми), логическими и вещественными ключами (см. ниже "кластеризация объектов с не числовыми ключами")

Рассмотрим алогоритм сортировки со сложностью O(NlogN) и массив из N объектов (удовлетворяющих условиям 2 и 3). Выполним кластеризацию элементов и разобьем массив на k>0 групп (предельное значение k определяется природой сортируемых объектов). Отсортируем каждую группу объектов независимо от других. При этом (учитывая условие 1) мы потратим в среднем (N/k) log (N/k) операций.А на независимую сортировку всех k групп мы потратим в среднем k*[N/k log (N/k) ]. Преобразуя это выражение получим:N(logN - log k)=NlogN - Nlogk(2)

Из (2) видно, что суммарное число потраченных на сортировку операций снижается. Как не трудно заметить чем выше k (больше групп в исходном массиве), тем меньше итоговая (конечная) сложность сортировки. В купе с возможностью сортировки каждой группы элементов массива независимо, такой подход открывает путь для построения высокоэффективных алгоритмов сортировки без строгих ограничений на объем и природу сортируемых данных.

Сортировка массива таким способом требует предварительной обработки: кластеризации элементов. Наиболее эффективным алгоритмом кластеризации со сложностью O(n) является поразрядная сортировка (называемая другими авторами "Американский флаг" [VM2]). Данный класс алгоритмов способен к потоковой обработке массива в один проход, а также допускает паралеллизацию (с неизбежным интенсивным обменом между процессами по кластеризации). В качестве дополнительного бонуса предварительной обработки сортируемого массива является возможность проведения аналитики массива. Возможно массив вообще не требует сортировки или элементы одного класса (группы) уже упорядочены и данная группа может быть исключена из обработки.

кластеризация объектов с нечисловыми ключами. Кластеризация объектов, ключи которых представляют собой текстовые строки, может производиться по первым буквам строк, порождая тем самым 26 групп. Возможно снизить количество групп, объединяя уже сами буквы в группы. Например, все объекты с ключами, начинающимися с букв a-f относят к группе 0, начинающимися с букв g-l относят к группе 1, и так далее.

Кластеризация объектов с ключами в виде вещественных чисел легко производится по диапазонам значений (предварительно необходимо уточнить реальные границы ключей: мин.макс).

Измерения. Автором был проверен предложенный подход на практике, с получением следующих результатов (табл.1). Сбор статистики осуществлял специальный прокси-массив, подсчитывающий реальное число обменов, сравнений и обращений к ключам сортируемых объектов. Сортировка производилась при помощи алгоритмов "Плавная сортировка" и "Сортировка восходящей кучей".В полученных данных учтены также операции, выполненные на этапах кластеризации массива.

Таблица 1. Подсчет выполненных операций над массивами при разных техниках сортировки:

N/k

обменов

сравнений

доступов к указателю

перезаписей указателя

обращений к ключу

64/1

356

390

611

452

908

64/16

232

260

356

264

520

512/1

4319

4683

6366

5087

10390

512/16

3422

3691

4442

3678

7382

10k/1

126855

135333

166854

141855

290666

10k/16

110872

118006

130868

115872

236012

1M/16

19283612

20130313

23283611

20783612

42260626

1M/16

17668264

18434186

19668260

18168264

36868372

первая строка таблицы - затраты на сортировку массива из 64 элементов целиком
вторая строка таблицы - затраты на сортировку того же массива, после разделения на 16 частей
третья строка - затраты на сортировку массива 512 элементов
четвертая строка - затраты на сортировку того же массива после разделения на 16 частей
последние две строки таблицы - данные о сортировке массива из 1 млн. элементов (стоит обратить внимание, что сортировка такого массива 16ю независимыми частями сокращает на 14 млн. число выполняемых операций даже с учетом операций по предварительной кластеризации поразрядной сортировкой)

Ссылки на другие работы по теме:

[KS1] Параллельная сортировка методом пузырька на CUDA
[KILY1] Сортировка массивов фиксированной длины с применением SIMD
[PatZ1] Параллельная сортировка данных в GPU
[Ms1] Параллельный метод сортировки массива std::thread
[VM2] Сортировка «Американский флаг»

Комментарии (18)


  1. KaminskyIlya Автор
    16.02.2025 01:11

    Стоит заметить, что автор испугался далее повышать размер сортируемого массива (хотя на млрд. элементов в тестах замахивался), боясь получить отрицательные значения выгоды, и ненароком разорвать структуру пространства-бытия.


    1. dyadyaSerezha
      16.02.2025 01:11

      Да, речь же шла о супербольших объёмах, а тут все закончилось несчастным миллионом. Странно.

      Как дилетант, интересуюсь - а нельзя ли сортировать такие массивы на каждом из сотен (тысяч?) маленьких процессоров GPU параллельно? Или так и делается уже?


      1. KaminskyIlya Автор
        16.02.2025 01:11

        Представьте общественности алгоритм сортировки на GPU составных объектов, физически размещаемых на файловых носителях. большинство авторов больше как сортировку натуральных чисел предложить не могут. даже сортировку строк никто не предложил. А все потому что при сортировке подобных данных вас необходимо работать с указателями. Т.е. вы фактически перемещаете указатели, а сами данные физически не сортируются. В этом проблема


        1. KaminskyIlya Автор
          16.02.2025 01:11

          Прошу меня простить за столь резкий комментарий. Вы не обязаны ничего представлять общественности, наоборот это я должен доказывать. Но проблемы сортировки на GPU что я озвучил выше - никуда не делись. И я действительно не встречал годные реализации сортировок на GPU для, например, использования СУБД


          1. unreal_undead2
            16.02.2025 01:11

            Гугл сходу такое выдаёт - в реальной жизни не работает?


        1. dyadyaSerezha
          16.02.2025 01:11

          Ничего не понял. В чем проблема перемещать сами объекты в файлах? Как напишешь, так и будет.


      1. KaminskyIlya Автор
        16.02.2025 01:11

        Да тут один человек сказал, что выигрыш мизерный . Даже если бы я представил измерения для 100ГБ элементов - вряд ли это бы повлияло на столь высокую оценку.

        На основе представленных измерений тенденция видна. Как доказательство, что концепция верна .


      1. KaminskyIlya Автор
        16.02.2025 01:11

        Я специально для вас опубликую код и замеры на массиве из 1 миллиарда записей. Но чуть позже. Просто мне необходимо привести свой экспериментальный проект в божеский вид. А то в процессе экспериментов там лапша по-гуще ассамблера сейчас

        Такой размер подойдёт? Сортируемые данные будут находить на диске в момент выполнения сортировки. В оперативную память моего старенького компьютера они не поместится. Поэтому сортироваться будут по факту индексы( указатели на json-записи).Сортируемые данные будут являться json-записями , ключом сортировки - будет текстовое поле объекта длиной до 16 символов.

        Сделаем замеры скорости в операциях и по времени. Вас устроит такой вариант теста?


        1. dyadyaSerezha
          16.02.2025 01:11

          16 байт? Не катит. Катит до 2К, причём лексически,причем с учётом locale.

          И про индексы не понял. То надо перемещать данные, то не надо...


  1. Dhwtj
    16.02.2025 01:11

    N(logN - log k)=NlogN - Nlogk

    То есть даже теоретически с учётом размеров величин выигрыш мизерный


    1. KaminskyIlya Автор
      16.02.2025 01:11

      Я правильно понял что экономия 16 млн операций при сортировке 1млн объектов, для вас мизерная?

      Это не считая возможности без проблем выполнить сортировку параллельно в 16 потоков.


      1. Dhwtj
        16.02.2025 01:11

        N = миллиарды

        K = тысячи

        Посчитайте


        1. KaminskyIlya Автор
          16.02.2025 01:11

          Хорошо. Пусть N=10^9, k=1000,

          Тогда Nlogk=10^9*10(примерно 10 - логарифма тут двоичный).

          Итого почти 10 млд. Операций экономия. Среди этих операций-операции сравнения ключей, которые для составных ключей могут быть очень не быстрыми. Стоимость сравнения строк, например, O(n). Так что насчет мизерной выгоды нисколько не согласен.


          1. Dhwtj
            16.02.2025 01:11

            10^9*30 - 10^9*10

            Экономия 30%

            Ну оочень много, ага


    1. dmitry_rozhkov
      16.02.2025 01:11

      Но если все кластеры сортировать параллельно, то по времени выигрыш должен получиться больше. Нет?


      1. Dhwtj
        16.02.2025 01:11

        Нет


  1. Kealon
    16.02.2025 01:11

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

    1. Классически, можно поделить и петлями quick sort.

    2. Плавная сортировка в худшем случае O(N (log(N)^2))


    1. KaminskyIlya Автор
      16.02.2025 01:11

      Интересное мнение. А можете провести примеры когда кластеризация невозможна?

      Сложность плавной сортировки брал на Википедии. Об оценке, что вы привели - не слышал.

      Спасибо за конструктивный комментарий