Найшвидший спосіб сортування 10 чисел? (цифри 32 бітні)


211

Я вирішую проблему, і це включає сортування 10 чисел (int32) дуже швидко. Моїй програмі потрібно сортувати 10 номерів мільйони разів якнайшвидше. Я відбираю набір даних з мільярдів елементів, і кожен раз, коли мені потрібно вибирати з нього 10 чисел (спрощено) і сортувати їх (і робити висновки зі списку відсортованих 10 елементів).

В даний час я використовую сортування вставки, але я думаю, що я міг би реалізувати дуже швидкий спеціальний алгоритм сортування для моєї конкретної проблеми з 10 чисел, яка б перемогла сортування вставки.

Хтось має уявлення про те, як підійти до цієї проблеми?


14
Як би грубо це не звучало, серія вкладених ifтверджень повинна працювати найкраще. Уникайте петель.
Джон Алексій

8
Чи очікуєте ви, що вам буде надано номери з будь-яким ухилом у наборі перестановок, або вони будуть рівномірно розподілені? Чи буде якийсь зв’язок між впорядкуванням одного списку та наступного?
Дуглас Заре

4
Весь набір даних (з мільярдами чисел) розподіляється відповідно до закону Бенфорда, але коли я вибираю елементи з випадкового набору з цього набору, їх більше немає (я думаю).
bodacydo

13
Ви можете прочитати цю stackoverflow.com/q/2786899/995714
phuclv

11
Якщо ви вибираєте випадковим чином з мільярдів елементів, то цілком можливо, що затримка витягування даних може мати більший вплив, ніж час, необхідний для сортування вибраних елементів, навіть якщо весь набір даних знаходиться в оперативній пам'яті. Ви можете перевірити вплив, використовуючи показник ефективності, вибираючи дані послідовно проти випадкових випадків.
Стів С.

Відповіді:


213

(Слідкуйте за пропозицією HelloWorld вивчити сортування мереж.)

Здається, що мережа порівняння / обміну 29-ти є найшвидшим способом зробити сортування на 10 входів. Я використав мережу, виявлену Ваксманом у 1969 році, для цього прикладу в Javascript, який повинен бути перекладений безпосередньо на C, оскільки це лише список ifтверджень, порівнянь та свопів.

function sortNet10(data) {	// ten-input sorting network by Waksman, 1969
    var swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
    return(data);
}

alert(sortNet10([5,7,1,8,4,3,6,9,2,0]));

Ось графічне зображення мережі, розділене на незалежні фази. Щоб скористатися паралельною обробкою, 5-4-3-4-4-4-3-2 групування може бути змінено на 4-4-4-4-4-4-3-2.
10 сортувальна мережа (Waksman, 1969)

10-вхідна мережа сортування (Waksman, 1969) перегрупується


69
пропозиція; використовувати макрос swap. як#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
Пітер Кордес

9
Чи можна логічно показати, що це мінімум?
corsiKa

8
@corsiKa Так, сортування мереж було предметом досліджень з перших днів інформатики. У багатьох випадках оптимальні рішення відомі десятиліттями. Дивіться en.wikipedia.org/wiki/Sorting_network
m69 '' примхливий і небажаний ''

8
Я зробив Jsperf для тестування і можу підтвердити, що Network Sort є в 20 разів швидшим, ніж рідний сорт браузера. jsperf.com/fastest-10-number-sort
Даніель,

9
@Katai Це знищить будь-яку оптимізацію, яку може виробляти ваш компілятор. Погана ідея. Прочитайте це для отримання додаткової інформації en.wikipedia.org/wiki/…
Antzi

88

Якщо ви маєте справу з цим фіксованим розміром, подивіться на сортування мереж . Ці алгоритми мають фіксований час виконання і не залежать від їх введення. Для вашого випадку у вас немає таких накладних витрат, які мають деякі алгоритми сортування.

Сортування бітонів - це реалізація такої мережі. Цей варіант найкраще працює з len (n) <= 32 на процесорі. Що стосується великих входів, ви можете подумати про перехід до GPU. https://en.wikipedia.org/wiki/Sorting_network

Btw, хороша сторінка для порівняння алгоритмів сортування - це ця тут (хоча її відсутні bitonic sort.

http://www.sorting-algorithms.com


3
@ ErickG.Hagstrom Є багато рішень; якщо вони використовують 29 порівнянь, вони однаково ефективні. Я використовував рішення Ваксмана з 1969 року; він, мабуть, був першим, хто виявив версію порівняння 29.
m69 '' примхливий і непривітний ''

1
Так, @ m69. Їх понад мільйон. Рішення Waksman має довжину 29 і глибину 9. Рішення, яке я пов’язував, - це поліпшення в порівнянні з розміром глибини: довжина = 29, глибина = 8. Звичайно, при реалізації в С глибина не має значення.
Ерік Г. Хагстром

4
@ ErickG.Hagstrom Мабуть, існує 87 рішень із глибиною 7, перше з яких було знайдено Кнутом у 1973 році, але я не зміг знайти жодне з них за допомогою швидкого Google. larc.unt.edu/ian/pubs/9-input.pdf (див. Висновок, стор.14)
m69 '' примхливий і непривітний ''

4
@ ErickG.Hagstrom: глибина може не змінити значення "на рівні С", але, мабуть, після того, як компілятор і процесор закінчать це, є певний шанс, що він буде частково паралелізований в процесорі, і тому менша глибина може допомогти. Залежно від процесора, звичайно: деякі процесори відносно прості і виконують одне за іншим, тоді як деякі процесори можуть робити кілька операцій під час польоту, зокрема, ви можете отримати дуже різну продуктивність для будь-яких навантажень і зберігати в стек, які потрібні в щоб маніпулювати 10 змінними, залежно від того, як вони виконані.
Стів Джессоп

1
@ ErickG.Hagstrom Це було не відразу зрозуміло з паперу Ian Parberry, але мережі глибинних 7 мають довжину більше 29. Див. Knuth, "Мистецтво програмування комп'ютера Vol.III", §5.3.4, рис. . 49 і 51.
m69 '' примхливий і непривітний ''

33

Використовуйте мережу сортування, яка має порівняння в групах по 4, так що ви можете це робити в регістрах SIMD. Пара упакованих інструкцій min / max реалізує функцію компаратора, що запаковується. Вибачте, у мене немає часу шукати сторінку, яку я пам’ятаю про це, але сподіваюся, пошук у сортувальних мережах SIMD або SSE щось вимкнеться.

x86 SSE має упаковані 32-бітові цілі min та max інструкції для векторів чотирьох 32-бітових входів. AVX2 (Haswell і пізніші версії) мають те саме, але для векторів 256b 8 ints. Існують також ефективні інструкції щодо переміщення.

Якщо у вас багато незалежних малих сортів, паралельно з векторами можна зробити 4 або 8 сортів. Esp якщо ви вибираєте елементи випадковим чином (таким чином, дані, які будуть відсортовані, так чи інакше не будуть суміжними у пам'яті), ви можете уникнути перетасовок і просто порівняти в потрібному вам порядку. 10 регістрів для зберігання всіх даних із 4 (AVX2: 8) списків з 10 ints все ще залишають 6 регістрів для місця подряпин.

Мережі сортування вектора менш ефективні, якщо вам також потрібно сортувати пов'язані дані. У такому випадку, найефективнішим способом, здається, є використання упакованого порівняння, щоб отримати маску змін елементів, і використовувати цю маску для змішування векторів (посилань на) пов'язаних даних.


26

А як щодо розкрученого сорту без відгалуження?

#include <iostream>
#include <algorithm>
#include <random>

//return the index of the minimum element in array a
int min(const int * const a) {
  int m = a[0];
  int indx = 0;
  #define TEST(i) (m > a[i]) && (m = a[i], indx = i ); 
  //see http://stackoverflow.com/a/7074042/2140449
  TEST(1);
  TEST(2);
  TEST(3);
  TEST(4);
  TEST(5);
  TEST(6);
  TEST(7);
  TEST(8);
  TEST(9);
  #undef TEST
  return indx;
}

void sort( int * const a ){
  int work[10];
  int indx;
  #define GET(i) indx = min(a); work[i] = a[indx]; a[indx] = 2147483647; 
  //get the minimum, copy it to work and set it at max_int in a
  GET(0);
  GET(1);
  GET(2);
  GET(3);
  GET(4);
  GET(5);
  GET(6);
  GET(7);
  GET(8);
  GET(9);
  #undef GET
  #define COPY(i) a[i] = work[i];
  //copy back to a
  COPY(0);
  COPY(1);
  COPY(2);
  COPY(3);
  COPY(4);
  COPY(5);
  COPY(6);
  COPY(7);
  COPY(8);
  COPY(9);
  #undef COPY
}

int main() {
  //generating and printing a random array
  int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
  std::random_device rd;
  std::mt19937 g(rd());
  std::shuffle( a, a+10, g);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  }
  std::cout << std::endl;

  //sorting and printing again
  sort(a);
  for (int i = 0; i < 10; i++) {
    std::cout << a[i] << ' ';
  } 

  return 0;
}

http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6

Єдині відповідні рядки - перші два #define.

Він використовує два списки і повністю повторно перевіряє перший десять разів, що було б погано реалізованим видом вибору, однак це дозволяє уникнути гілок та циклів змінної довжини, що може компенсувати сучасні процесори та такий невеликий набір даних.


Орієнтир

Я орієнтувався на мережу сортування, і мій код здається повільнішим. Однак я намагався видалити розгортання та копію. Запуск цього коду:

#include <iostream>
#include <algorithm>
#include <random>
#include <chrono>

int min(const int * const a, int i) {
  int m = a[i];
  int indx = i++;
  for ( ; i<10; i++) 
    //see http://stackoverflow.com/a/7074042/2140449
    (m > a[i]) && (m = a[i], indx = i ); 
  return indx;
}

void sort( int * const a ){
  for (int i = 0; i<9; i++)
    std::swap(a[i], a[min(a,i)]); //search only forward
}


void sortNet10(int * const data) {  // ten-input sorting network by Waksman, 1969
    int swap;
    if (data[0] > data[5]) { swap = data[0]; data[0] = data[5]; data[5] = swap; }
    if (data[1] > data[6]) { swap = data[1]; data[1] = data[6]; data[6] = swap; }
    if (data[2] > data[7]) { swap = data[2]; data[2] = data[7]; data[7] = swap; }
    if (data[3] > data[8]) { swap = data[3]; data[3] = data[8]; data[8] = swap; }
    if (data[4] > data[9]) { swap = data[4]; data[4] = data[9]; data[9] = swap; }
    if (data[0] > data[3]) { swap = data[0]; data[0] = data[3]; data[3] = swap; }
    if (data[5] > data[8]) { swap = data[5]; data[5] = data[8]; data[8] = swap; }
    if (data[1] > data[4]) { swap = data[1]; data[1] = data[4]; data[4] = swap; }
    if (data[6] > data[9]) { swap = data[6]; data[6] = data[9]; data[9] = swap; }
    if (data[0] > data[2]) { swap = data[0]; data[0] = data[2]; data[2] = swap; }
    if (data[3] > data[6]) { swap = data[3]; data[3] = data[6]; data[6] = swap; }
    if (data[7] > data[9]) { swap = data[7]; data[7] = data[9]; data[9] = swap; }
    if (data[0] > data[1]) { swap = data[0]; data[0] = data[1]; data[1] = swap; }
    if (data[2] > data[4]) { swap = data[2]; data[2] = data[4]; data[4] = swap; }
    if (data[5] > data[7]) { swap = data[5]; data[5] = data[7]; data[7] = swap; }
    if (data[8] > data[9]) { swap = data[8]; data[8] = data[9]; data[9] = swap; }
    if (data[1] > data[2]) { swap = data[1]; data[1] = data[2]; data[2] = swap; }
    if (data[3] > data[5]) { swap = data[3]; data[3] = data[5]; data[5] = swap; }
    if (data[4] > data[6]) { swap = data[4]; data[4] = data[6]; data[6] = swap; }
    if (data[7] > data[8]) { swap = data[7]; data[7] = data[8]; data[8] = swap; }
    if (data[1] > data[3]) { swap = data[1]; data[1] = data[3]; data[3] = swap; }
    if (data[4] > data[7]) { swap = data[4]; data[4] = data[7]; data[7] = swap; }
    if (data[2] > data[5]) { swap = data[2]; data[2] = data[5]; data[5] = swap; }
    if (data[6] > data[8]) { swap = data[6]; data[6] = data[8]; data[8] = swap; }
    if (data[2] > data[3]) { swap = data[2]; data[2] = data[3]; data[3] = swap; }
    if (data[4] > data[5]) { swap = data[4]; data[4] = data[5]; data[5] = swap; }
    if (data[6] > data[7]) { swap = data[6]; data[6] = data[7]; data[7] = swap; }
    if (data[3] > data[4]) { swap = data[3]; data[3] = data[4]; data[4] = swap; }
    if (data[5] > data[6]) { swap = data[5]; data[5] = data[6]; data[6] = swap; }
}


std::chrono::duration<double> benchmark( void(*func)(int * const), const int seed ) {
  std::mt19937 g(seed);
  int a[10] = {10,11,12,13,14,15,16,17,18,19};
  std::chrono::high_resolution_clock::time_point t1, t2; 
  t1 = std::chrono::high_resolution_clock::now();
  for (long i = 0; i < 1e7; i++) {
    std::shuffle( a, a+10, g);
    func(a);
  }
  t2 = std::chrono::high_resolution_clock::now();
  return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
}

int main() {
  std::random_device rd;
  for (int i = 0; i < 10; i++) {
    const int seed = rd();
    std::cout << "seed = " << seed << std::endl;
    std::cout << "sortNet10: " << benchmark(sortNet10, seed).count() << std::endl;
    std::cout << "sort:      " << benchmark(sort,      seed).count() << std::endl;
  }
  return 0;
}

Я постійно отримую кращі результати для сортування вибору, що не потребує гілок, порівняно з мережею сортування.

$ gcc -v
gcc version 5.2.0 (GCC) 
$ g++ -std=c++11 -Ofast sort.cpp && ./a.out
seed = -1727396418
sortNet10: 2.24137
sort:      2.21828
seed = 2003959850
sortNet10: 2.23914
sort:      2.21641
seed = 1994540383
sortNet10: 2.23782
sort:      2.21778
seed = 1258259982
sortNet10: 2.25199
sort:      2.21801
seed = 1821086932
sortNet10: 2.25535
sort:      2.2173
seed = 412262735
sortNet10: 2.24489
sort:      2.21776
seed = 1059795817
sortNet10: 2.29226
sort:      2.21777
seed = -188551272
sortNet10: 2.23803
sort:      2.22996
seed = 1043757247
sortNet10: 2.2503
sort:      2.23604
seed = -268332483
sortNet10: 2.24455
sort:      2.24304

4
Результати не дуже вражають, але насправді я б очікував. Мережа сортування мінімізує порівняння, а не свопи. Коли всі значення вже є в кеш-пам'яті, порівняння набагато дешевше, ніж свопи, тому сортування вибору (що мінімізує кількість свопів) має верх. (А не те, що багато інших порівняння: мережа з 29 compasions до вибору 29 свопи?; Проти роду з 45 порівняннями і в більшості 9 свопів)
наприклад ,

7
Ой і у неї є гілки - якщо тільки лінія не for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i ); є надзвичайно оптимізованою. (коротке замикання зазвичай є формою розгалуження)
приклад

1
@EugeneRyabtsev теж, але він живиться абсолютно однаковими випадковими послідовностями, тому його слід скасувати. Я намагався змінити std::shuffleз for (int n = 0; n<10; n++) a[n]=g();. Час виконання вдвічі зменшився, а мережа зараз швидша.
DarioP

Як це співвідноситься з Libc ++ s std::sort?
gnzlbg

1
@gnzlbg Я також спробував std::sort, але це було так погано, що я навіть не включив його до еталону. Я здогадуюсь, що з крихітними наборами даних є великі витрати.
DarioP

20

Питання не говорить про те, що це якась веб-програма. Єдине, що кинуло мене на очі:

Я відбираю набір даних з мільярдів елементів, і кожен раз, коли мені потрібно вибирати з нього 10 чисел (спрощено) і сортувати їх (і робити висновки зі списку відсортованих 10 елементів).

Як інженер програмного та апаратного забезпечення, він абсолютно кричить мені "FPGA" . Я не знаю, які висновки потрібно робити з відсортованого набору чисел або звідки беруться дані, але я знаю, що було б майже тривіально обробити десь від ста мільйонів до мільярда цих «сортувальних і- проаналізувати "операції в секунду . У минулому я робив роботу з секвенуванням ДНК з допомогою FPGA. Майже неможливо перемогти потужність процесора FPGA, коли проблема добре підходить для такого типу рішення.

На деякому рівні єдиним обмежуючим фактором стає те, наскільки швидко ви можете передавати дані в FPGA і як швидко ви можете їх отримати.

В якості орієнтиру я розробив високопродуктивний процесор зображення в режимі реального часу, який отримував 32-бітні RGB-зображення зі швидкістю близько 300 мільйонів пікселів в секунду. Дані передаються через фільтри FIR, матричні матриці, таблиці пошуку, блоки просторового виявлення ребер та ряд інших операцій, перш ніж вийти з іншого кінця. Все це на порівняно невеликому FPGA Xilinx Virtex2 з внутрішньою тактовою частотою, що охоплює приблизно від 33 МГц до, якщо я правильно пам'ятаю, 400 МГц. О так, він також мав реалізацію контролера DDR2 і керував двома банками пам'яті DDR2.

FPGA може вивести десять 32-бітових цифр на кожному тактовому переході під час роботи на сотнях МГц. На початку операції буде невелика затримка, оскільки дані заповнюють обробний трубопровід / с. Після цього ви повинні мати можливість отримувати один результат за годинник. Або більше, якщо обробку можна паралелізувати шляхом реплікації трубопроводу сортування та аналізу. Рішення, в принципі, майже тривіальне.

Справа в тому, що якщо програма не пов'язана з ПК, і потік даних та обробка "сумісні" з FPGA-рішенням (як автономне, так і як картка з сумісним процесором в машині), ви не зможете перейти. вміти перемогти досягнутий рівень продуктивності за допомогою програмного забезпечення, написаного будь-якою мовою, незалежно від алгоритму.

Редагувати:

Просто запустили швидкий пошук і знайшли папір, який може вам бути корисним. Схоже, це датується 2012 роком. Ви можете зробити набагато кращі показники сьогодні (і навіть тоді). Ось:

Сортування мереж на FPGA


10

Нещодавно я написав невеликий клас, який використовує алгоритм Бозе-Нельсона для створення мережі сортування за часом компіляції.

З його допомогою можна створити дуже швидкий сорт для 10 чисел.

/**
 * A Functor class to create a sort for fixed sized arrays/containers with a
 * compile time generated Bose-Nelson sorting network.
 * \tparam NumElements  The number of elements in the array or container to sort.
 * \tparam T            The element type.
 * \tparam Compare      A comparator functor class that returns true if lhs < rhs.
 */
template <unsigned NumElements, class Compare = void> class StaticSort
{
    template <class A, class C> struct Swap
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            T t = Compare()(v0, v1) ? v0 : v1; // Min
            v1 = Compare()(v0, v1) ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A> struct Swap <A, void>
    {
        template <class T> inline void s(T &v0, T &v1)
        {
            // Explicitly code out the Min and Max to nudge the compiler
            // to generate branchless code.
            T t = v0 < v1 ? v0 : v1; // Min
            v1 = v0 < v1 ? v1 : v0; // Max
            v0 = t;
        }

        inline Swap(A &a, const int &i0, const int &i1) { s(a[i0], a[i1]); }
    };

    template <class A, class C, int I, int J, int X, int Y> struct PB
    {
        inline PB(A &a)
        {
            enum { L = X >> 1, M = (X & 1 ? Y : Y + 1) >> 1, IAddL = I + L, XSubL = X - L };
            PB<A, C, I, J, L, M> p0(a);
            PB<A, C, IAddL, J + M, XSubL, Y - M> p1(a);
            PB<A, C, IAddL, J, XSubL, M> p2(a);
        }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 1>
    {
        inline PB(A &a) { Swap<A, C> s(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 1, 2>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J); Swap<A, C> s1(a, I - 1, J - 1); }
    };

    template <class A, class C, int I, int J> struct PB <A, C, I, J, 2, 1>
    {
        inline PB(A &a) { Swap<A, C> s0(a, I - 1, J - 1); Swap<A, C> s1(a, I, J - 1); }
    };

    template <class A, class C, int I, int M, bool Stop = false> struct PS
    {
        inline PS(A &a)
        {
            enum { L = M >> 1, IAddL = I + L, MSubL = M - L};
            PS<A, C, I, L, (L <= 1)> ps0(a);
            PS<A, C, IAddL, MSubL, (MSubL <= 1)> ps1(a);
            PB<A, C, I, IAddL, L, MSubL> pb(a);
        }
    };

    template <class A, class C, int I, int M> struct PS <A, C, I, M, true>
    {
        inline PS(A &a) {}
    };

public:
    /**
     * Sorts the array/container arr.
     * \param  arr  The array/container to be sorted.
     */
    template <class Container> inline void operator() (Container &arr) const
    {
        PS<Container, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };

    /**
     * Sorts the array arr.
     * \param  arr  The array to be sorted.
     */
    template <class T> inline void operator() (T *arr) const
    {
        PS<T*, Compare, 1, NumElements, (NumElements <= 1)> ps(arr);
    };
};

#include <iostream>
#include <vector>

int main(int argc, const char * argv[])
{
    enum { NumValues = 10 };

    // Arrays
    {
        int rands[NumValues];
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    std::cout << "\n";

    // STL Vector
    {
        std::vector<int> rands(NumValues);
        for (int i = 0; i < NumValues; ++i) rands[i] = rand() % 100;
        std::cout << "Before Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
        StaticSort<NumValues> staticSort;
        staticSort(rands);
        std::cout << "After Sort: \t";
        for (int i = 0; i < NumValues; ++i) std::cout << rands[i] << " ";
        std::cout << "\n";
    }

    return 0;
}

Зауважте, що замість if (compare) swapзаяви ми явно кодуємо потрійні оператори для min та max. Це допоможе підштовхнути компілятор до використання безрозгалужуваного коду.

Орієнтири

Наступні орієнтири складені з clang -O3 та пройшли в моєму середині 2012 року в ефірі MacBook.

Сортування випадкових даних

Порівнюючи його з кодом DarioP, ось кількість мілісекунд, необхідних для сортування 1 мільйона 32-бітових int масивів розміром 10:

Твердокодований сортувальний чистий 10: 88,774 мс Шаблоноване
Бозе-Нельсон сортування 10: 27,815 мс

Використовуючи цей шаблонний підхід, ми також можемо генерувати мережі сортування за часом компіляції для іншої кількості елементів.

Час (в мілісекундах) на сортування 1 мільйона масивів різного розміру.
Кількість мілісекунд для масивів розміром 2, 4, 8 становить 1,943, 8,655, 20,246 відповідно.
C ++ Шаблонні статичні сортировки Бозе-Нельсона

Кредити Гленну Тейтельбауму за нерозподілений сортування вставки.

Ось середні такти на один сорт для невеликих масивів з 6 елементів. Код еталону та приклади можна знайти у цьому питанні:
Найшвидший сорт фіксованої довжини 6 int масив

Direct call to qsort library function       : 326.81
Naive implementation (insertion sort)       : 132.98
Insertion Sort (Daniel Stutzbach)           : 104.04
Insertion Sort Unrolled                     : 99.64
Insertion Sort Unrolled (Glenn Teitelbaum)  : 81.55
Rank Order                                  : 44.01
Rank Order with registers                   : 42.40
Sorting Networks (Daniel Stutzbach)         : 88.06
Sorting Networks (Paul R)                   : 31.64
Sorting Networks 12 with Fast Swap          : 29.68
Sorting Networks 12 reordered Swap          : 28.61
Reordered Sorting Network w/ fast swap      : 24.63
Templated Sorting Network (this class)      : 25.37

Він виконує так само швидко, як найшвидший приклад у питанні про 6 елементів.

Продуктивність для сортування відсортованих даних

Часто вхідні масиви можуть бути вже відсортовані або в основному відсортовані.
У таких випадках вибір вставки може бути кращим вибором.

введіть тут опис зображення

Ви можете вибрати відповідний алгоритм сортування залежно від даних.

Код, який використовується для орієнтирів, можна знайти тут .


Будь-який шанс ви можете додати порівняння для мого альго нижче?
Glenn Teitelbaum

@GlennTeitelbaum будь-який шанс, що ви додали це до своїх орієнтирів та розкрили засоби та результати?
сіра борода

Kudos для додавання даних про сортування впорядкованого введення.
сіра борода

У деяких системах v1 = v0 < v1 ? v1 : v0; // Maxможе все ще галузі, в цьому випадку він може бути замінений , v1 += v0 - tтому що , якщо tє , v0то v1 + v0 -t == v1 + v0 - v0 == v1ще tє v1іv1 + v0 -t == v1 + v0 - v1 == v0
Гленн Тейтельбаум

Тернар зазвичай складається в інструкцію maxssабо minssінструкцію про сучасні компілятори. Але у випадках, коли це не працює, можна застосовувати інші способи заміни. :)
Векторизований

5

Незважаючи на те, що мережеве сортування має великі шанси на швидкість на малих масивах, іноді ви не можете перемогти сортування вставки, якщо належним чином оптимізовано. Наприклад, пакетна вставка з 2 елементами:

{
    final int a=in[0]<in[1]?in[0]:in[1];
    final int b=in[0]<in[1]?in[1]:in[0];
    in[0]=a;
    in[1]=b;
}
for(int x=2;x<10;x+=2)
{
    final int a=in[x]<in[x+1]?in[x]:in[x+1];
    final int b=in[x]<in[x+1]?in[x+1]:in[x];
    int y= x-1;

    while(y>=0&&in[y]>b)
    {
        in[y+2]= in[y];
        --y;
    }
    in[y+2]=b;
    while(y>=0&&in[y]>a)
    {
        in[y+1]= in[y];
        --y;
    }
    in[y+1]=a;
}

Не знаєте, чому ви повторюєте in[y+2]= in[y];, друкарський помилок?
Гленн Тейтельбаум

Нічого собі, як я це зробив? І як пройшло так довго, щоб хтось помітив? Відповідь: Це не помилка: я адаптував інший алгоритм, який мав і ключ, і масив значень.
warren

3

Ви можете повністю розгорнутися insertion sort

Для того, щоб зробити це простіше, templateможна використовувати рекурсивні системи без накладних функцій. Оскільки це вже є template, intможе бути і templateпараметром. Це також дозволяє створити масив кодування, крім 10 тривіальних, для створення.

Зауважте, що сортування int x[10]виклику відбувається, insert_sort<int, 9>::sort(x);оскільки клас використовує індекс останнього елемента. Це може бути завершено, але це було б більше коду для читання.

template <class T, int NUM>
class insert_sort;

template <class T>
class insert_sort<T,0>
// stop template recursion
// sorting 1 item is a no-op
{
public:
    static void place(T *x) {}
    static void sort(T * x) {}
};

template <class T, int NUM>
class insert_sort
// use template recursion to do insertion sort
// NUM is the index of the last item, eg. for x[10] call <9>
{
public:
    static void place(T *x)
    {
        T t1=x[NUM-1];
        T t2=x[NUM];
        if (t1 > t2)
        {
            x[NUM-1]=t2;
            x[NUM]=t1;
            insert_sort<T,NUM-1>::place(x);
        }
    }
    static void sort(T * x)
    {
        insert_sort<T,NUM-1>::sort(x); // sort everything before
        place(x);                    // put this item in
    }
};

У моєму тестуванні це було швидше, ніж приклади мережі сортування.


0

З причин, подібних до тих, що я описав тут , наступні функції сортування, sort6_iterator()і sort10_iterator_local(), повинні добре виконувати, де звідси була взята мережа сортування :

template<class IterType> 
inline void sort10_iterator(IterType it) 
{
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   auto data##a=*(data+a);
#define DD2(a,b) auto data##a=*(data+a), data##b=*(data+b);
#define CB1(a)   *(data+a)=data##a;
#define CB2(a,b) *(data+a)=data##a;*(data+b)=data##b;
  DD2(1,4) SORT2(1,4) DD2(7,8) SORT2(7,8) DD2(2,3) SORT2(2,3) DD2(5,6) SORT2(5,6) DD2(0,9) SORT2(0,9) 
  SORT2(2,5) SORT2(0,7) SORT2(8,9) SORT2(3,6) 
  SORT2(4,9) SORT2(0,1) 
  SORT2(0,2) CB1(0) SORT2(6,9) CB1(9) SORT2(3,5) SORT2(4,7) SORT2(1,8) 
  SORT2(3,4) SORT2(5,8) SORT2(6,7) SORT2(1,2) 
  SORT2(7,8) CB1(8) SORT2(1,3) CB1(1) SORT2(2,5) SORT2(4,6) 
  SORT2(2,3) CB1(2) SORT2(6,7) CB1(7) SORT2(4,5) 
  SORT2(3,4) CB2(3,4) SORT2(5,6) CB2(5,6) 
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

Щоб викликати цю функцію, я передав їй std::vectorітератор.


0

Для сортування вставки потрібно в середньому 29,6 порівнянь, щоб сортувати 10 входів з найкращим випадком 9 та найгіршим з 45 (з урахуванням введення, який знаходиться в зворотному порядку).

Для {9,6,1} снарядів знадобиться в середньому 25,5 порівнянь, щоб сортувати 10 входів. Найкращий випадок - 14 порівнянь, найгірший - 34, а для сортування зворотного вводу потрібно 22.

Тож використання Shellsort замість вставки сортування зменшує середній випадок на 14%. Хоча найкращий випадок збільшується на 56%, найгірший випадок зменшується на 24%, що є важливим у додатках, коли важливо підтримувати ефективність найгіршого випадку. Зворотний випадок зменшується на 51%.

Оскільки вам здається, що ви знайомі з сортуванням вставки, ви можете реалізувати алгоритм як мережу сортування для {9,6}, а потім застосувати сортування вставки ({1}) після цього:

i[0] with i[9]    // {9}

i[0] with i[6]    // {6}
i[1] with i[7]    // {6}
i[2] with i[8]    // {6}
i[3] with i[9]    // {6}

i[0 ... 9]        // insertion sort
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.