Самый быстрый тип фиксированной длины 6 int array

Отвечая на другой вопрос (этот), я наткнулся на интересную подзадачу. Какой самый быстрый способ сортировки массива из 6 целых чисел?

Как вопрос очень низкого уровня:

  • мы не можем предполагать, что библиотеки доступны (и сам вызов имеет свою стоимость), только простой C
  • чтобы избежать опустошения конвейера команд (который имеет очень высокую стоимость), мы, вероятно, должны минимизировать переходы, переходы и любые другие виды прерывания потока управления (например, те, которые скрыты за точками последовательности в && или ||).
  • пространство ограничено, и минимизация регистров и использование памяти является проблемой, в идеале сортировка на месте, вероятно, лучше всего.

На самом деле этот вопрос - своего рода гольф, цель которого не в том, чтобы минимизировать длину источника, а во время выполнения. Я называю это "кодом Зенинга", как он используется в названии книги "Оптимизация кода Zen " Майкла Абраша и ее продолжений.

Что касается того, почему это интересно, есть несколько слоев:

  • пример прост и легок для понимания и измерения, не требующий много навыков
  • это показывает эффекты выбора хорошего алгоритма для проблемы, но также эффекты компилятора и основного оборудования.

Вот моя эталонная (наивная, не оптимизированная) реализация и мой набор тестов.

#include <stdio.h>

static __inline__ int sort6(int * d){

    char j, i, imin;
    int tmp;
    for (j = 0 ; j < 5 ; j++){
        imin = j;
        for (i = j + 1; i < 6 ; i++){
            if (d[i] < d[imin]){
                imin = i;
            }
        }
        tmp = d[j];
        d[j] = d[imin];
        d[imin] = tmp;
    }
}

static __inline__ unsigned long long rdtsc(void)
{
  unsigned long long int x;
     __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
     return x;
}

int main(int argc, char ** argv){
    int i;
    int d[6][5] = {
        {1, 2, 3, 4, 5, 6},
        {6, 5, 4, 3, 2, 1},
        {100, 2, 300, 4, 500, 6},
        {100, 2, 3, 4, 500, 6},
        {1, 200, 3, 4, 5, 600},
        {1, 1, 2, 1, 2, 1}
    };

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6 ; i++){
        sort6(d[i]);
        /*
         * printf("d%d : %d %d %d %d %d %d\n", i,
         *  d[i][0], d[i][6], d[i][7],
         *  d[i][8], d[i][9], d[i][10]);
        */
    }
    cycles = rdtsc() - cycles;
    printf("Time is %d\n", (unsigned)cycles);
}

Необработанные результаты

Поскольку число вариантов становится большим, я собрал их все в наборе тестов, который можно найти здесь. Благодаря Кевину Сток, используемые тесты немного менее наивны, чем показанные выше. Вы можете скомпилировать и выполнить его в своей среде. Мне весьма интересно поведение на разных целевых архитектурах/компиляторах. (Хорошо, ребята, поместите это в ответах, я буду +1 каждый участник нового набора результатов).

Я дал ответ Даниэлю Штутцбаху (для игры в гольф) год назад, поскольку он был источником самого быстрого решения в то время (сортировка сетей).

Linux 64 бит, gcc 4.6.1 64 бит, Intel Core 2 Duo E8400, -O2

  • Прямой вызов функции библиотеки qsort: 689,38
  • Наивная реализация (вставка сортировки): 285.70
  • Сортировка вставок (Даниэль Штутцбах): 142,12
  • Развернутая сортировка вставок: 125.47
  • Порядок ранга: 102.26
  • Ранг Порядок с регистрами: 58.03
  • Сортировка сетей (Даниэль Штутцбах): 111,68
  • Сортировка сетей (Paul R): 66,36
  • Сортировка сетей 12 с быстрой заменой: 58,86
  • Сортировка сетей 12 переупорядоченный своп: 53,74
  • Сортировка сетей 12 переупорядочен Простая замена: 31,54
  • Переупорядоченная сеть сортировки с быстрой заменой: 31,54
  • Переупорядоченная сеть сортировки с быстрой заменой V2: 33,63
  • Сортированный пузырь (Паоло Бонзини): 48,85
  • Развернутая сортировка вставок (Паоло Бонзини): 75,30

Linux 64 бит, gcc 4.6.1 64 бит, Intel Core 2 Duo E8400, -O1

  • Прямой вызов функции библиотеки qsort: 705,93
  • Наивная реализация (вставка сортировки): 135.60
  • Сортировка вставок (Даниэль Штутцбах): 142,11
  • Развернутая сортировка вставок: 126,75
  • Порядок ранга: 46.42
  • Порядок ранжирования с регистрами: 43,58
  • Сортировка сетей (Даниэль Штутцбах): 115,57
  • Сортировка сетей (Paul R): 64,44
  • Сортировка сетей 12 с быстрой заменой: 61,98
  • Сортировка сетей 12 переупорядоченный своп: 54,67
  • Сортировка сетей 12 переупорядочен Простая замена: 31,54
  • Переупорядоченная сеть сортировки с быстрой заменой: 31,24
  • Переупорядоченная сеть сортировки с быстрой заменой V2: 33,07
  • Сортированный пузырь (Паоло Бонзини): 45,79
  • Развернутая сортировка вставок (Паоло Бонзини): 80,15

Я включил результаты -O1 и -O2, потому что неожиданно для нескольких программ O2 менее эффективен, чем O1. Интересно, какая специфическая оптимизация имеет этот эффект?

Комментарии к предлагаемым решениям

Сортировка вставок (Даниэль Штутцбах)

Как и ожидалось, минимизация веток действительно хорошая идея.

Сортировка сетей (Даниэль Штутцбах)

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

Сортировка сетей (Пол R)

Лучший пока что. Фактический код, который я использовал для тестирования, находится здесь. Пока не знаю, почему это почти в два раза быстрее, чем в другой сети сортировки. Передача параметров? Быстро макс?

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

По предложению Даниэля Штутцбаха, я объединил его сеть сортировки с 12 свопами и быстрый обмен без ответвлений (код здесь). Это действительно быстрее, лучше всего с небольшим запасом (примерно 5%), как и следовало ожидать, используя 1 своп меньше.

Интересно также отметить, что обмен без ответвлений, по-видимому, намного (в 4 раза) менее эффективен, чем простой, используемый в архитектуре PPC.

Вызов библиотеки qsort

Чтобы дать еще одну контрольную точку, я также попытался, как предлагалось, просто вызвать библиотеку qsort (код здесь). Как и ожидалось, он намного медленнее: в 10-30 раз медленнее... как стало очевидно с новым набором тестов, основная проблема, похоже, заключается в начальной загрузке библиотеки после первого вызова, и она не так плохо сравнивается с другими версия. Это только в 3-20 раз медленнее в моем Linux. В некоторых архитектурах, используемых для тестирования другими, это кажется даже быстрее (я действительно удивлен тем, что библиотека qsort использует более сложный API).

Порядок ранга

Рекс Керр предложил другой совершенно другой метод: для каждого элемента массива вычисляют непосредственно его конечную позицию. Это эффективно, потому что порядок вычисления не требует ветвления. Недостаток этого метода состоит в том, что он занимает в три раза больше памяти массива (одна копия массива и переменные для хранения ранговых порядков). Результаты производительности очень удивительны (и интересны). На моей эталонной архитектуре с 32-битной ОС и Intel Core2 Quad E8300 число циклов было чуть ниже 1000 (как в случае сортировки сетей с разветвленной ветвью). Но когда он был скомпилирован и выполнен на моем 64-битном компьютере (Intel Core2 Duo), он работал намного лучше: он стал самым быстрым до сих пор. Я наконец выяснил истинную причину. Мой 32-битный бокс использует gcc 4.4.1 и мой 64-битный бокс gcc 4.4.3, и последний, кажется, намного лучше оптимизирует этот конкретный код (для других предложений было очень мало различий).

Обновить:

Как видно из опубликованных выше рисунков, этот эффект все еще усиливался в более поздних версиях gcc, и ранговый порядок стал в два раза быстрее, чем в любой другой альтернативе.

Сортировка сетей 12 с переупорядоченным свопом

Удивительная эффективность предложения Rex Kerr с gcc 4.4.3 заставила меня задуматься: как может программа, использующая в 3 раза больше памяти, работать быстрее, чем сети без ветвей сортировки? Моя гипотеза состояла в том, что у него было меньше зависимостей типа чтения после записи, что позволяло лучше использовать планировщик суперскалярных команд в x86. Это дало мне идею: изменить порядок перестановок, чтобы минимизировать зависимости чтения после записи. Проще говоря: когда вы делаете SWAP(1, 2); SWAP(0, 2); SWAP(1, 2); SWAP(0, 2); Вы должны дождаться завершения первого обмена, прежде чем выполнять второй, потому что оба имеют доступ к общей ячейке памяти. Когда вы делаете SWAP(1, 2); SWAP(4, 5); SWAP(1, 2); SWAP(4, 5); процессор может выполнять оба параллельно. Я попробовал, и все работает как положено, сортировочные сети работают примерно на 10% быстрее.

Сортировка сетей 12 с помощью простого обмена

Через год после первоначального поста Стейнар Х. Гандерсон предложил не пытаться перехитрить компилятор и сохранить простой код подкачки. Это действительно хорошая идея, поскольку полученный код работает примерно на 40% быстрее! Он также предложил обмен, оптимизированный вручную с использованием встроенного кода сборки x86, который может сэкономить еще несколько циклов. Самое удивительное (в нем говорится о психологии программистов) то, что год назад никто из бывших в употреблении не пробовал эту версию свопинга. Код, который я использовал для тестирования, находится здесь. Другие предлагали другие способы написания быстрого свопинга на C, но он дает те же характеристики, что и простой с приличным компилятором.

"Лучший" код теперь выглядит следующим образом:

static inline void sort6_sorting_network_simple_swap(int * d){
#define min(x, y) (x<y?x:y)
#define max(x, y) (x<y?y:x) 
#define SWAP(x,y) { const int a = min(d[x], d[y]); \
                    const int b = max(d[x], d[y]); \
                    d[x] = a; d[y] = b; }
    SWAP(1, 2);
    SWAP(4, 5);
    SWAP(0, 2);
    SWAP(3, 5);
    SWAP(0, 1);
    SWAP(3, 4);
    SWAP(1, 4);
    SWAP(0, 3);
    SWAP(2, 5);
    SWAP(1, 3);
    SWAP(2, 4);
    SWAP(2, 3);
#undef SWAP
#undef min
#undef max
}

Если мы считаем, что наш тестовый набор (и, да, он довольно плохой, просто преимущество в том, что он короткий, простой и легкий для понимания того, что мы измеряем), среднее число циклов полученного кода для одного вида будет меньше 40 циклов ( Выполнено 6 тестов). Это означает, что каждый своп в среднем составляет 4 цикла. Я называю это удивительно быстро. Возможны ли другие улучшения?

Ответ 1

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

Знаете ли вы что-нибудь о входных данных? Некоторые алгоритмы будут работать лучше с некоторыми видами данных. Например, сортировка вставки лучше выполняется для отсортированных или почти отсортированных данных, поэтому это будет лучший выбор, если есть вероятность превышения по сравнению со средним значением почти отсортированных данных.

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

Здесь выполняется реализация сортировки вставки:

static __inline__ int sort6(int *d){
        int i, j;
        for (i = 1; i < 6; i++) {
                int tmp = d[i];
                for (j = i; j >= 1 && tmp < d[j-1]; j--)
                        d[j] = d[j-1];
                d[j] = tmp;
        }
}

Вот как я построил бы сортирующую сеть. Сначала используйте этот сайт для создания минимального набора макросов SWAP для сети соответствующей длины. Обертывание этого в функции дает мне:

static __inline__ int sort6(int * d){
#define SWAP(x,y) if (d[y] < d[x]) { int tmp = d[x]; d[x] = d[y]; d[y] = tmp; }
    SWAP(1, 2);
    SWAP(0, 2);
    SWAP(0, 1);
    SWAP(4, 5);
    SWAP(3, 5);
    SWAP(3, 4);
    SWAP(0, 3);
    SWAP(1, 4);
    SWAP(2, 5);
    SWAP(2, 4);
    SWAP(1, 3);
    SWAP(2, 3);
#undef SWAP
}

Ответ 2

Здесь реализация с использованием сортировочных сетей:

inline void Sort2(int *p0, int *p1)
{
    const int temp = min(*p0, *p1);
    *p1 = max(*p0, *p1);
    *p0 = temp;
}

inline void Sort3(int *p0, int *p1, int *p2)
{
    Sort2(p0, p1);
    Sort2(p1, p2);
    Sort2(p0, p1);
}

inline void Sort4(int *p0, int *p1, int *p2, int *p3)
{
    Sort2(p0, p1);
    Sort2(p2, p3);
    Sort2(p0, p2);  
    Sort2(p1, p3);  
    Sort2(p1, p2);  
}

inline void Sort6(int *p0, int *p1, int *p2, int *p3, int *p4, int *p5)
{
    Sort3(p0, p1, p2);
    Sort3(p3, p4, p5);
    Sort2(p0, p3);  
    Sort2(p2, p5);  
    Sort4(p1, p2, p3, p4);  
}

Для этого вам понадобятся очень эффективные для этого разветвленные min и max реализации, так как это фактически то, к чему этот код сводится - последовательность операций min и max (всего 13 из них). Я оставляю это как упражнение для читателя.

Обратите внимание, что эта реализация легко поддается векторизации (например, SIMD - большинство SIMD ISAs имеют векторные инструкции min/max), а также к реализациям графического процессора (например, CUDA - бездисковое отсутствие проблем с деформированием warp и т.д.).

См. Также: Быстрая реализация алгоритма для сортировки очень малого списка

Ответ 3

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

inline void sort6(int *d) {
  int e[6];
  memcpy(e,d,6*sizeof(int));
  int o0 = (d[0]>d[1])+(d[0]>d[2])+(d[0]>d[3])+(d[0]>d[4])+(d[0]>d[5]);
  int o1 = (d[1]>=d[0])+(d[1]>d[2])+(d[1]>d[3])+(d[1]>d[4])+(d[1]>d[5]);
  int o2 = (d[2]>=d[0])+(d[2]>=d[1])+(d[2]>d[3])+(d[2]>d[4])+(d[2]>d[5]);
  int o3 = (d[3]>=d[0])+(d[3]>=d[1])+(d[3]>=d[2])+(d[3]>d[4])+(d[3]>d[5]);
  int o4 = (d[4]>=d[0])+(d[4]>=d[1])+(d[4]>=d[2])+(d[4]>=d[3])+(d[4]>d[5]);
  int o5 = 15-(o0+o1+o2+o3+o4);
  d[o0]=e[0]; d[o1]=e[1]; d[o2]=e[2]; d[o3]=e[3]; d[o4]=e[4]; d[o5]=e[5];
}

Ответ 4

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

Глядя на сборку, сгенерированную gcc 4.5.2, я заметил, что нагрузки и магазины выполняются для каждого свопа, что действительно не нужно. Было бы лучше загрузить 6 значений в регистры, отсортировать их и сохранить их обратно в память. Я приказал, чтобы нагрузки в магазинах были как можно ближе к тому, чтобы регистры были сначала необходимы и использовались в последний раз. Я также использовал SWAP-макрос Steinar H. Gunderson. Обновление: я переключился на макрос SWAP Paolo Bonzini, который gcc конвертирует в нечто похожее на Gunderson, но gcc может лучше упорядочить инструкции, поскольку они не указаны как явная сборка.

Я использовал тот же порядок подкачки, что и переупорядоченная сеть подкачки, которую вы выбрали как наиболее эффективный, хотя может быть и лучший порядок. Если я найду еще какое-то время, я создам и проведу кучу перестановок.

Я изменил код тестирования, чтобы рассмотреть более 4000 массивов и показать среднее количество циклов, необходимых для сортировки каждого из них. На i5-650 я получаю ~ 34.1 циклов/сортировку (используя -O3) по сравнению с исходной переупорядоченной сетью сортировки, получающей ~ 65.3 циклов/сортировку (с использованием -O1, бит -O2 и -O3).

#include <stdio.h>

static inline void sort6_fast(int * d) {
#define SWAP(x,y) { int dx = x, dy = y, tmp; tmp = x = dx < dy ? dx : dy; y ^= dx ^ tmp; }
    register int x0,x1,x2,x3,x4,x5;
    x1 = d[1];
    x2 = d[2];
    SWAP(x1, x2);
    x4 = d[4];
    x5 = d[5];
    SWAP(x4, x5);
    x0 = d[0];
    SWAP(x0, x2);
    x3 = d[3];
    SWAP(x3, x5);
    SWAP(x0, x1);
    SWAP(x3, x4);
    SWAP(x1, x4);
    SWAP(x0, x3);
    d[0] = x0;
    SWAP(x2, x5);
    d[5] = x5;
    SWAP(x1, x3);
    d[1] = x1;
    SWAP(x2, x4);
    d[4] = x4;
    SWAP(x2, x3);
    d[2] = x2;
    d[3] = x3;

#undef SWAP
#undef min
#undef max
}

static __inline__ unsigned long long rdtsc(void)
{
    unsigned long long int x;
    __asm__ volatile ("rdtsc; shlq $32, %%rdx; orq %%rdx, %0" : "=a" (x) : : "rdx");
    return x;
}

void ran_fill(int n, int *a) {
    static int seed = 76521;
    while (n--) *a++ = (seed = seed *1812433253 + 12345);
}

#define NTESTS 4096
int main() {
    int i;
    int d[6*NTESTS];
    ran_fill(6*NTESTS, d);

    unsigned long long cycles = rdtsc();
    for (i = 0; i < 6*NTESTS ; i+=6) {
        sort6_fast(d+i);
    }
    cycles = rdtsc() - cycles;
    printf("Time is %.2lf\n", (double)cycles/(double)NTESTS);

    for (i = 0; i < 6*NTESTS ; i+=6) {
        if (d[i+0] > d[i+1] || d[i+1] > d[i+2] || d[i+2] > d[i+3] || d[i+3] > d[i+4] || d[i+4] > d[i+5])
            printf("d%d : %d %d %d %d %d %d\n", i,
                    d[i+0], d[i+1], d[i+2],
                    d[i+3], d[i+4], d[i+5]);
    }
    return 0;
}

Я изменил измененный набор тестов, чтобы также сообщать о часах в сортировке и запускать больше тестов (функция cmp была обновлена ​​для обработки переполнения целых чисел), вот результаты по некоторым различным архитектурам. Я попытался протестировать процессор AMD, но rdtsc не является надежным на X6 1100T, который у меня есть.

Clarkdale (i5-650)
==================
Direct call to qsort library function      635.14   575.65   581.61   577.76   521.12
Naive implementation (insertion sort)      538.30   135.36   134.89   240.62   101.23
Insertion Sort (Daniel Stutzbach)          424.48   159.85   160.76   152.01   151.92
Insertion Sort Unrolled                    339.16   125.16   125.81   129.93   123.16
Rank Order                                 184.34   106.58   54.74    93.24    94.09
Rank Order with registers                  127.45   104.65   53.79    98.05    97.95
Sorting Networks (Daniel Stutzbach)        269.77   130.56   128.15   126.70   127.30
Sorting Networks (Paul R)                  551.64   103.20   64.57    73.68    73.51
Sorting Networks 12 with Fast Swap         321.74   61.61    63.90    67.92    67.76
Sorting Networks 12 reordered Swap         318.75   60.69    65.90    70.25    70.06
Reordered Sorting Network w/ fast swap     145.91   34.17    32.66    32.22    32.18

Kentsfield (Core 2 Quad)
========================
Direct call to qsort library function      870.01   736.39   723.39   725.48   721.85
Naive implementation (insertion sort)      503.67   174.09   182.13   284.41   191.10
Insertion Sort (Daniel Stutzbach)          345.32   152.84   157.67   151.23   150.96
Insertion Sort Unrolled                    316.20   133.03   129.86   118.96   105.06
Rank Order                                 164.37   138.32   46.29    99.87    99.81
Rank Order with registers                  115.44   116.02   44.04    116.04   116.03
Sorting Networks (Daniel Stutzbach)        230.35   114.31   119.15   110.51   111.45
Sorting Networks (Paul R)                  498.94   77.24    63.98    62.17    65.67
Sorting Networks 12 with Fast Swap         315.98   59.41    58.36    60.29    55.15
Sorting Networks 12 reordered Swap         307.67   55.78    51.48    51.67    50.74
Reordered Sorting Network w/ fast swap     149.68   31.46    30.91    31.54    31.58

Sandy Bridge (i7-2600k)
=======================
Direct call to qsort library function      559.97   451.88   464.84   491.35   458.11
Naive implementation (insertion sort)      341.15   160.26   160.45   154.40   106.54
Insertion Sort (Daniel Stutzbach)          284.17   136.74   132.69   123.85   121.77
Insertion Sort Unrolled                    239.40   110.49   114.81   110.79   117.30
Rank Order                                 114.24   76.42    45.31    36.96    36.73
Rank Order with registers                  105.09   32.31    48.54    32.51    33.29
Sorting Networks (Daniel Stutzbach)        210.56   115.68   116.69   107.05   124.08
Sorting Networks (Paul R)                  364.03   66.02    61.64    45.70    44.19
Sorting Networks 12 with Fast Swap         246.97   41.36    59.03    41.66    38.98
Sorting Networks 12 reordered Swap         235.39   38.84    47.36    38.61    37.29
Reordered Sorting Network w/ fast swap     115.58   27.23    27.75    27.25    26.54

Nehalem (Xeon E5640)
====================
Direct call to qsort library function      911.62   890.88   681.80   876.03   872.89
Naive implementation (insertion sort)      457.69   236.87   127.68   388.74   175.28
Insertion Sort (Daniel Stutzbach)          317.89   279.74   147.78   247.97   245.09
Insertion Sort Unrolled                    259.63   220.60   116.55   221.66   212.93
Rank Order                                 140.62   197.04   52.10    163.66   153.63
Rank Order with registers                  84.83    96.78    50.93    109.96   54.73
Sorting Networks (Daniel Stutzbach)        214.59   220.94   118.68   120.60   116.09
Sorting Networks (Paul R)                  459.17   163.76   56.40    61.83    58.69
Sorting Networks 12 with Fast Swap         284.58   95.01    50.66    53.19    55.47
Sorting Networks 12 reordered Swap         281.20   96.72    44.15    56.38    54.57
Reordered Sorting Network w/ fast swap     128.34   50.87    26.87    27.91    28.02

Ответ 5

Тест-код довольно плохой; он переполняет исходный массив (не читают ли люди здесь предупреждения компилятора?), printf печатает неправильные элементы, он использует .byte для rdtsc без уважительной причины, там только один запуск (!), там ничего не проверяют, что конечные результаты на самом деле правильные (поэтому очень легко "оптимизировать" что-то неточно неправильно), включенные тесты очень рудиментарны (нет отрицательных чисел?), и нет ничего, что могло бы заставить компилятор просто отбросить всю функцию как мертвый код.

Это, как говорится, также довольно легко улучшает решение битовой сети; просто измените материал min/max/SWAP на

#define SWAP(x,y) { int tmp; asm("mov %0, %2 ; cmp %1, %0 ; cmovg %1, %0 ; cmovg %2, %1" : "=r" (d[x]), "=r" (d[y]), "=r" (tmp) : "0" (d[x]), "1" (d[y]) : "cc"); }

и для меня это получается примерно на 65% (Debian gcc 4.4.5 с -O2, amd64, Core i7).

Ответ 6

Я наткнулся на этот вопрос из Google несколько дней назад, потому что мне также пришлось быстро отсортировать массив фиксированной длины из 6 целых чисел. В моем случае, однако, мои целые числа составляют всего 8 бит (вместо 32), и у меня нет строгого требования использовать только C. Я думал, что буду делиться своими результатами так или иначе, в случае, если они могут быть полезны кому-то...

Я реализовал вариант сортировки сети в сборке, который использует SSE для векторизации операций сравнения и свопинга, насколько это возможно. Для полного сортировки массива требуется шесть "проходов". Я использовал новый механизм для прямого преобразования результатов PCMPGTB (векторизованного сравнения) для перетасовки параметров для PSHUFB (векторизованный своп), используя только PADDB (векторизованное дополнение), а в некоторых случаях также команду PAND (побитовое И).

Этот подход также имел побочный эффект, дающий действительно бесветренную функцию. Инструкции о прыжках отсутствуют.

Похоже, что эта реализация примерно на 38% быстрее, чем реализация, которая в настоящее время отмечена как самый быстрый вариант в вопросе ( "Сортировка сетей 12 с простой заменой" ). Я модифицировал эту реализацию для использования элементов массива char во время моего тестирования, чтобы сделать сравнение справедливым.

Следует отметить, что этот подход может применяться к любому размеру массива до 16 элементов. Я ожидаю, что преимущество относительной скорости над альтернативами будет увеличиваться для больших массивов.

Код написан в MASM для процессоров x86_64 с SSSE3. Функция использует "новое" соглашение на использование Windows x64. Вот оно...

PUBLIC simd_sort_6

.DATA

ALIGN 16

pass1_shuffle   OWORD   0F0E0D0C0B0A09080706040503010200h
pass1_add       OWORD   0F0E0D0C0B0A09080706050503020200h
pass2_shuffle   OWORD   0F0E0D0C0B0A09080706030405000102h
pass2_and       OWORD   00000000000000000000FE00FEFE00FEh
pass2_add       OWORD   0F0E0D0C0B0A09080706050405020102h
pass3_shuffle   OWORD   0F0E0D0C0B0A09080706020304050001h
pass3_and       OWORD   00000000000000000000FDFFFFFDFFFFh
pass3_add       OWORD   0F0E0D0C0B0A09080706050404050101h
pass4_shuffle   OWORD   0F0E0D0C0B0A09080706050100020403h
pass4_and       OWORD   0000000000000000000000FDFD00FDFDh
pass4_add       OWORD   0F0E0D0C0B0A09080706050403020403h
pass5_shuffle   OWORD   0F0E0D0C0B0A09080706050201040300h
pass5_and       OWORD 0000000000000000000000FEFEFEFE00h
pass5_add       OWORD   0F0E0D0C0B0A09080706050403040300h
pass6_shuffle   OWORD   0F0E0D0C0B0A09080706050402030100h
pass6_add       OWORD   0F0E0D0C0B0A09080706050403030100h

.CODE

simd_sort_6 PROC FRAME

    .endprolog

    ; pxor xmm4, xmm4
    ; pinsrd xmm4, dword ptr [rcx], 0
    ; pinsrb xmm4, byte ptr [rcx + 4], 4
    ; pinsrb xmm4, byte ptr [rcx + 5], 5
    ; The benchmarked 38% faster mentioned in the text was with the above slower sequence that tied up the shuffle port longer.  Same on extract
    ; avoiding pins/extrb also means we don't need SSE 4.1, but SSSE3 CPUs without SSE4.1 (e.g. Conroe/Merom) have slow pshufb.
    movd    xmm4, dword ptr [rcx]
    pinsrw  xmm4,  word ptr [rcx + 4], 2  ; word 2 = bytes 4 and 5


    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass1_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass1_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass2_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass2_and]
    paddb xmm5, oword ptr [pass2_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass3_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass3_and]
    paddb xmm5, oword ptr [pass3_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass4_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass4_and]
    paddb xmm5, oword ptr [pass4_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass5_shuffle]
    pcmpgtb xmm5, xmm4
    pand xmm5, oword ptr [pass5_and]
    paddb xmm5, oword ptr [pass5_add]
    pshufb xmm4, xmm5

    movdqa xmm5, xmm4
    pshufb xmm5, oword ptr [pass6_shuffle]
    pcmpgtb xmm5, xmm4
    paddb xmm5, oword ptr [pass6_add]
    pshufb xmm4, xmm5

    ;pextrd dword ptr [rcx], xmm4, 0    ; benchmarked with this
    ;pextrb byte ptr [rcx + 4], xmm4, 4 ; slower version
    ;pextrb byte ptr [rcx + 5], xmm4, 5
    movd   dword ptr [rcx], xmm4
    pextrw  word ptr [rcx + 4], xmm4, 2  ; x86 is little-endian, so this is the right order

    ret

simd_sort_6 ENDP

END

Вы можете скомпилировать это для исполняемого объекта и связать его с вашим проектом C. Инструкции о том, как это сделать в Visual Studio, можно прочитать в этой статье. Вы можете использовать следующий прототип C для вызова функции из вашего кода C:

void simd_sort_6(char *values);

Ответ 7

Пока мне действительно нравится макрос подкачки:

#define min(x, y) (y ^ ((x ^ y) & -(x < y)))
#define max(x, y) (x ^ ((x ^ y) & -(x < y)))
#define SWAP(x,y) { int tmp = min(d[x], d[y]); d[y] = max(d[x], d[y]); d[x] = tmp; }

Я вижу улучшение (которое может сделать хороший компилятор):

#define SWAP(x,y) { int tmp = ((x ^ y) & -(y < x)); y ^= tmp; x ^= tmp; }

Мы принимаем во внимание, как работают min и max и вытягивают общее подвыражение явно. Это полностью исключает макросы min и max.

Ответ 8

Я портировал тестовый пакет на машину архитектуры PPC, которую я не могу идентифицировать (не нужно касаться кода, просто увеличивайте итерации теста, используйте 8 тестовых примеров, чтобы избежать загрязнения результатов с помощью модов и заменить специфичный x86 rdtsc ):

Прямой вызов функции библиотеки qsort: 101

Наивная реализация (сортировка вставки): 299

Вставка Сортировка (Daniel Stutzbach): 108

Вставка Сортировка развернута: 51

Сортировка сетей (Daniel Stutzbach): 26

Сортировка сетей (Paul R): 85

Сортировка сетей 12 с быстрой заменой: 117

Сортировка сетей 12 переупорядоченных свопов: 116

Порядок ранга: 56

Ответ 9

Никогда не оптимизируйте min/max без бенчмаркинга и посмотрите на сборку, созданную компилятором. Если я позволю GCC оптимизировать min с условными инструкциями перемещения, я получаю ускорение на 33%:

#define SWAP(x,y) { int dx = d[x], dy = d[y], tmp; tmp = d[x] = dx < dy ? dx : dy; d[y] ^= dx ^ tmp; }

(280 против 420 циклов в тестовом коде). Выполнение max с?: Более или менее одинаково, почти потеряно в шуме, но выше это немного быстрее. Этот SWAP быстрее работает как с GCC, так и с Clang.

Компиляторы также выполняют исключительную работу по распределению регистров и анализу псевдонимов, эффективно перемещая d [x] в локальные переменные авансом и только копируя обратно в память в конце. Фактически, они делают это даже лучше, чем если бы вы полностью работали с локальными переменными (например, d0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3], d4 = d[4], d5 = d[5]). Я пишу это, потому что вы принимаете сильную оптимизацию и все же пытаетесь перехитрить компилятор на min/max.:)

Кстати, я пробовал Clang и GCC. Они выполняют ту же оптимизацию, но из-за различий в расписании два имеют некоторые вариации в результатах, не могут сказать действительно, что происходит быстрее или медленнее. GCC работает быстрее в сортировочных сетях, Clang на квадратичных сортировках.

Просто для полноты, возможны разбросанные сортировки и сортировки в виде пузырей. Вот сортировка пузырьков:

SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4); SWAP(4,5);
SWAP(0,1); SWAP(1,2); SWAP(2,3); SWAP(3,4);
SWAP(0,1); SWAP(1,2); SWAP(2,3);
SWAP(0,1); SWAP(1,2);
SWAP(0,1);

и вот сортировка вставки:

//#define ITER(x) { if (t < d[x]) { d[x+1] = d[x]; d[x] = t; } }
//Faster on x86, probably slower on ARM or similar:
#define ITER(x) { d[x+1] ^= t < d[x] ? d[x] ^ d[x+1] : 0; d[x] = t < d[x] ? t : d[x]; }
static inline void sort6_insertion_sort_unrolled_v2(int * d){
    int t;
    t = d[1]; ITER(0);
    t = d[2]; ITER(1); ITER(0);
    t = d[3]; ITER(2); ITER(1); ITER(0);
    t = d[4]; ITER(3); ITER(2); ITER(1); ITER(0);
    t = d[5]; ITER(4); ITER(3); ITER(2); ITER(1); ITER(0);

Эта сортировка вставки быстрее, чем у Даниэля Штуцбаха, и особенно хороша на графическом процессоре или компьютере с предикацией, потому что ITER может выполняться только с тремя инструкциями (против 4 для SWAP). Например, вот строка t = d[2]; ITER(1); ITER(0); в сборке ARM:

    MOV    r6, r2
    CMP    r6, r1
    MOVLT  r2, r1
    MOVLT  r1, r6
    CMP    r6, r0
    MOVLT  r1, r0
    MOVLT  r0, r6

Для шести элементов сортировка вставки конкурирует с сетью сортировки (12 свопов против 15 итераций балансирует 4 команды/своп против 3 инструкций/итераций); сорт пузыря, конечно, медленнее. Но это будет не так, когда размер возрастает, поскольку сортировка вставки - это O (n ^ 2), а сортировочные сети - O (n log n).

Ответ 10

Смена XOR может быть полезна в ваших функциях обмена.

void xorSwap (int *x, int *y) {
     if (*x != *y) {
         *x ^= *y;
         *y ^= *x;
         *x ^= *y;
     }
 }

Если это может привести к слишком большому расхождению в вашем коде, но если у вас есть гарантия, что все ваши ints уникальны, это может быть удобно.

Ответ 11

С нетерпением ждем попыток разобраться в этом и изучая эти примеры, но сначала некоторые тайминги от моего 1,5 ГГц PPC Powerbook G4 с 1 ГБ оперативной памяти DDR. (Я заимствовал аналогичный rdtsc-подобный таймер для PPC от http://www.mcs.anl.gov/~kazutomo/rdtsc.html для таймингов.) Я запускал программу несколько раз, и абсолютные результаты но последовательным самым быстрым тестом была "Insertion Sort (Daniel Stutzbach)", с "Insertion Sort Unrolled" - вторая секунда.

Здесь последний набор раз:

**Direct call to qsort library function** : 164
**Naive implementation (insertion sort)** : 138
**Insertion Sort (Daniel Stutzbach)**     : 85
**Insertion Sort Unrolled**               : 97
**Sorting Networks (Daniel Stutzbach)**   : 457
**Sorting Networks (Paul R)**             : 179
**Sorting Networks 12 with Fast Swap**    : 238
**Sorting Networks 12 reordered Swap**    : 236
**Rank Order**                            : 116

Ответ 12

Вот мой вклад в этот поток: оптимизированный 1, 4 промежуточный shellsort для 6-членного int vector (valp), содержащий уникальные значения.

void shellsort (int *valp)
{      
  int c,a,*cp,*ip=valp,*ep=valp+5;

  c=*valp;    a=*(valp+4);if (c>a) {*valp=    a;*(valp+4)=c;}
  c=*(valp+1);a=*(valp+5);if (c>a) {*(valp+1)=a;*(valp+5)=c;}

  cp=ip;    
  do
  {
    c=*cp;
    a=*(cp+1);
    do
    {
      if (c<a) break;

      *cp=a;
      *(cp+1)=c;
      cp-=1;
      c=*cp;
    } while (cp>=valp);
    ip+=1;
    cp=ip;
  } while (ip<ep);
}

На моем ноутбуке HP dv7-3010so с двухъядерным Athlon M300 @2 Ghz (память DDR2) он выполняется в 165 тактах. Это среднее значение, рассчитываемое по времени каждой уникальной последовательности (всего 6!/720). Скомпилирован в Win32 с использованием OpenWatcom 1.8. Цикл по существу является сортировкой вставки и составляет 16 инструкций/длиной 37 байт.

У меня нет 64-разрядной среды для компиляции.

Ответ 13

Если сортировка вставки здесь достаточно конкурентоспособна, я бы рекомендовал попробовать shellsort. Я боюсь, что 6 элементов, вероятно, слишком малы, чтобы быть одним из лучших, но, возможно, стоит попробовать.

Пример кода, непроверенный, undebugged и т.д. Вы хотите настроить последовательность inc = 4 и inc - = 3, чтобы найти оптимальный (пример inc = 2, inc - = 1, например).

static __inline__ int sort6(int * d) {
    char j, i;
    int tmp;
    for (inc = 4; inc > 0; inc -= 3) {
        for (i = inc; i < 5; i++) {
            tmp = a[i];
            j = i;
            while (j >= inc && a[j - inc] > tmp) {
                a[j] = a[j - inc];
                j -= inc;
            }
            a[j] = tmp;
        }
    }
}

Я не думаю, что это победит, но если кто-то задаст вопрос о сортировке 10 элементов, кто знает...

Согласно Википедии, это может быть даже объединено с сортировочными сетями: Pratt, V (1979). Оболочки и сортировочные сети (выдающиеся диссертации в области компьютерных наук). Garland. ISBN 0-824-04406-1

Ответ 14

Этот вопрос становится довольно старым, но в действительности мне приходилось решать ту же проблему: быстрые агоримы для сортировки небольших массивов. Я подумал, что было бы неплохо поделиться своими знаниями. Когда я впервые начал использовать сортировочные сети, мне наконец удалось найти другие алгоритмы, для которых общее количество сравнений, выполняемых для сортировки каждой перестановки из 6 значений, было меньше, чем при сортировке сетей, и меньше, чем при сортировке вставки. Я не считал количество свопов; Я ожидаю, что это будет примерно эквивалентно (может быть, иногда немного выше).

Алгоритм sort6 использует алгоритм sort4, который использует алгоритм sort3. Вот реализация в некоторой легкой форме С++ (оригинал тяжелый шаблон, так что он может работать с любым итератором произвольного доступа и любой подходящей функцией сравнения).

Сортировка 3 значений

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

void sort3(int* array)
{
    if (array[1] < array[0]) {
        if (array[2] < array[0]) {
            if (array[2] < array[1]) {
                std::swap(array[0], array[2]);
            } else {
                int tmp = array[0];
                array[0] = array[1];
                array[1] = array[2];
                array[2] = tmp;
            }
        } else {
            std::swap(array[0], array[1]);
        }
    } else {
        if (array[2] < array[1]) {
            if (array[2] < array[0]) {
                int tmp = array[2];
                array[2] = array[1];
                array[1] = array[0];
                array[0] = tmp;
            } else {
                std::swap(array[1], array[2]);
            }
        }
    }
}

Он выглядит немного сложным, поскольку сортировка имеет более или менее одну ветвь для каждой возможной перестановки массива, используя 2 ~ 3 сравнения и не более 4 назначений для сортировки трех значений.

Сортировка 4 значений

Этот вызов вызывает sort3, затем выполняет развернутую сортировку вставки с последним элементом массива:

void sort4(int* array)
{
    // Sort the first 3 elements
    sort3(array);

    // Insert the 4th element with insertion sort 
    if (array[3] < array[2]) {
        std::swap(array[2], array[3]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[1] < array[0]) {
                std::swap(array[0], array[1]);
            }
        }
    }
}

Этот алгоритм выполняет от 3 до 6 сравнений и не более 5 свопов. Легко развернуть сортировку вставки, но мы будем использовать другой алгоритм для последнего сортировки...

Сортировка 6 значений

В этом случае используется развернутая версия того, что я назвал двойной сортировкой вставки. Название не так уж и велико, но это довольно описательно, вот как это работает:

  • Сортировка всех, кроме первого и последнего элементов массива.
  • Поменяйте сначала и элементы массива, если первый больше, чем последний.
  • Вставьте первый элемент в отсортированную последовательность спереди, а затем последний элемент со спины.

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

void sort6(int* array)
{
    // Sort everything but first and last elements
    sort4(array+1);

    // Switch first and last elements if needed
    if (array[5] < array[0]) {
        std::swap(array[0], array[5]);
    }

    // Insert first element from the front
    if (array[1] < array[0]) {
        std::swap(array[0], array[1]);
        if (array[2] < array[1]) {
            std::swap(array[1], array[2]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[4] < array[3]) {
                    std::swap(array[3], array[4]);
                }
            }
        }
    }

    // Insert last element from the back
    if (array[5] < array[4]) {
        std::swap(array[4], array[5]);
        if (array[4] < array[3]) {
            std::swap(array[3], array[4]);
            if (array[3] < array[2]) {
                std::swap(array[2], array[3]);
                if (array[2] < array[1]) {
                    std::swap(array[1], array[2]);
                }
            }
        }
    }
}

Мои тесты при каждой перестановке из 6 значений показывают, что эти алгоритмы всегда выполняют между 6 и 13 сравнениями. Я не вычислял количество выполняемых свопов, но я не ожидаю, что в худшем случае он будет выше 11.

Я надеюсь, что это поможет, даже если этот вопрос больше не может представлять собой настоящую проблему:)

РЕДАКТИРОВАТЬ: после того, как он поместит его в предоставленный ориентир, он будет медленнее, чем большинство интересных альтернатив. Он имеет тенденцию работать немного лучше, чем развернутая сортировка вставки, но это в значительной степени. В принципе, это не лучший вид для целых чисел, но может быть интересен для типов с дорогостоящей операцией сравнения.

Ответ 15

Я знаю, что я очень поздно, но мне было интересно экспериментировать с некоторыми другими решениями. Во-первых, я очистил эту пасту, скомпилировал ее и поместил в репозиторий. Я сохранил некоторые нежелательные решения как тупики, чтобы другие не пробовали. Среди них было мое первое решение, которое пыталось обеспечить однократное вычисление x1 > x2. После оптимизации он не быстрее других простых версий.

Я добавил циклическую версию сортировки ранжирования, так как мое собственное приложение этого исследования предназначено для сортировки 2-8 элементов, поэтому, поскольку существует переменное количество аргументов, необходим цикл. Вот почему я проигнорировал сетевые решения сортировки.

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

Затем я написал сортировку вставки, которая полностью находится в регистре AVX. На моей машине он на 25% быстрее, чем другие сортировки вставки, но на 100% медленнее, чем ранжирование. Я сделал это исключительно для эксперимента и не ожидал, что это лучше из-за разветвления в сортировке вставки.

static inline void sort6_insertion_sort_avx(int* d) {
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], 0, 0);
    __m256i index = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
    __m256i shlpermute = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6);
    __m256i sorted = _mm256_setr_epi32(d[0], INT_MAX, INT_MAX, INT_MAX,
            INT_MAX, INT_MAX, INT_MAX, INT_MAX);
    __m256i val, gt, permute;
    unsigned j;
     // 8 / 32 = 2^-2
#define ITER(I) \
        val = _mm256_permutevar8x32_epi32(src, _mm256_set1_epi32(I));\
        gt =  _mm256_cmpgt_epi32(sorted, val);\
        permute =  _mm256_blendv_epi8(index, shlpermute, gt);\
        j = ffs( _mm256_movemask_epi8(gt)) >> 2;\
        sorted = _mm256_blendv_epi8(_mm256_permutevar8x32_epi32(sorted, permute),\
                val, _mm256_cmpeq_epi32(index, _mm256_set1_epi32(j)))
    ITER(1);
    ITER(2);
    ITER(3);
    ITER(4);
    ITER(5);
    int x[8];
    _mm256_storeu_si256((__m256i*)x, sorted);
    d[0] = x[0]; d[1] = x[1]; d[2] = x[2]; d[3] = x[3]; d[4] = x[4]; d[5] = x[5];
#undef ITER
}

Затем я написал сортировку порядка ранжирования с использованием AVX. Это соответствует скорости других решений ранга, но не быстрее. Проблема здесь в том, что я могу вычислять индексы с помощью AVX, а затем мне приходится составлять таблицу индексов. Это связано с тем, что вычисление основано на назначении, а не на основе исходного кода. См. Преобразование из индексов, основанных на источнике, в индексы на основе назначений

static inline void sort6_rank_order_avx(int* d) {
    __m256i ror = _mm256_setr_epi32(5, 0, 1, 2, 3, 4, 6, 7);
    __m256i one = _mm256_set1_epi32(1);
    __m256i src = _mm256_setr_epi32(d[0], d[1], d[2], d[3], d[4], d[5], INT_MAX, INT_MAX);
    __m256i rot = src;
    __m256i index = _mm256_setzero_si256();
    __m256i gt, permute;
    __m256i shl = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 6, 6);
    __m256i dstIx = _mm256_setr_epi32(0,1,2,3,4,5,6,7);
    __m256i srcIx = dstIx;
    __m256i eq = one;
    __m256i rotIx = _mm256_setzero_si256();
#define INC(I)\
    rot = _mm256_permutevar8x32_epi32(rot, ror);\
    gt = _mm256_cmpgt_epi32(src, rot);\
    index = _mm256_add_epi32(index, _mm256_and_si256(gt, one));\
    index = _mm256_add_epi32(index, _mm256_and_si256(eq,\
                _mm256_cmpeq_epi32(src, rot)));\
    eq = _mm256_insert_epi32(eq, 0, I)
    INC(0);
    INC(1);
    INC(2);
    INC(3);
    INC(4);
    int e[6];
    e[0] = d[0]; e[1] = d[1]; e[2] = d[2]; e[3] = d[3]; e[4] = d[4]; e[5] = d[5];
    int i[8];
    _mm256_storeu_si256((__m256i*)i, index);
    d[i[0]] = e[0]; d[i[1]] = e[1]; d[i[2]] = e[2]; d[i[3]] = e[3]; d[i[4]] = e[4]; d[i[5]] = e[5];
}

Репо можно найти здесь: https://github.com/eyepatchParrot/sort6/

Ответ 16

Я считаю, что есть две части вашего вопроса.

  • Первый - определить оптимальный алгоритм. Это делается, по крайней мере, в этом случае, путем циклического перебора всех возможных порядков (их не так много), что позволяет вам вычислять точное минимальное, максимальное, среднее и стандартное отклонение сравнений и свопов. Имейте второе место или два удобных, а также.
  • Вторая задача - оптимизировать алгоритм. Многое можно сделать, чтобы преобразовать примеры кода учебника для обозначения и опре деления реальных алгоритмов. Если вы понимаете, что алгоритм не может быть оптимизирован настолько, насколько это необходимо, попробуйте занявшего второе место.

Я бы не стал слишком беспокоиться об освобождении конвейеров (предполагая текущий x86): предсказание ветки прошло долгий путь. Я бы беспокоился о том, что код и данные вписываются в одну строку кэша (возможно, два для кода). После того, как выборки задержек будут освежающе низкими, что компенсирует любой ларек. Это также означает, что ваш внутренний цикл будет, может быть, десятью инструкциями или так, что правильно там, где это должно быть (в моем алгоритме сортировки есть две разные внутренние петли, они - 10 команд /22 байта и 9/22 длинны соответственно). Предполагая, что код не содержит никаких div, вы можете быть уверены, что он будет ослепительно быстрым.

Ответ 17

Я обнаружил, что, по крайней мере, в моей системе функции sort6_iterator() и sort6_iterator_local() определенные ниже, sort6_iterator_local() как минимум так же быстро и часто заметно быстрее, чем вышеуказанный текущий держатель записи:

#define MIN(x, y) (x<y?x:y)
#define MAX(x, y) (x<y?y:x)

template<class IterType> 
inline void sort6_iterator(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(*(it + x), *(it + y)); \
  const auto b = MAX(*(it + x), *(it + y)); \
  *(it + x) = a; *(it + y) = b; }

  SWAP(1, 2) SWAP(4, 5)
  SWAP(0, 2) SWAP(3, 5)
  SWAP(0, 1) SWAP(3, 4)
  SWAP(1, 4) SWAP(0, 3)
  SWAP(2, 5) SWAP(1, 3)
  SWAP(2, 4)
  SWAP(2, 3)
#undef SWAP
}

Я передал эту функцию std::vector итератор в моем временном коде. Я подозреваю, что использование итераторов дает g++ определенные гарантии того, что может и не может произойти с памятью, на которую ссылается итератор, чего у него иначе не было бы, и именно эти гарантии позволяют g++ лучше оптимизировать сортировку код (который, если я правильно помню, также является одной из причин того, почему так много алгоритмов std, таких как std::sort(), обычно имеют такую неприлично хорошую производительность). Однако во время синхронизации я заметил, что контекст (т.е. Окружающий код), в котором был сделан вызов функции сортировки, оказал значительное влияние на производительность, что, вероятно, связано с тем, что функция встроена, а затем оптимизирована. Например, если программа была достаточно простой, то, как правило, не было большой разницы в производительности между передачей функции сортировки указатель и передачей ей итератора; в противном случае использование итераторов обычно приводило к заметно лучшей производительности и никогда (по моему опыту, по крайней мере, пока) никакой заметно худшей производительности. Я подозреваю, что это может быть потому, что g++ может глобально оптимизировать достаточно простой код.

Кроме того, sort6_iterator() иногда (опять же, в зависимости от контекста, в котором вызывается функция) постоянно превосходит следующую функцию сортировки, которая копирует данные в локальные переменные перед их сортировкой:

template<class IterType> 
inline void sort6_iterator_local(IterType it) 
{
#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  const auto b = MAX(data##x, data##y); \
  data##x = a; data##y = b; }
//DD = Define Data
#define DD1(a)   auto data##a = *(it + a);
#define DD2(a,b) auto data##a = *(it + a), data##b = *(it + b);
//CB = Copy Back
#define CB(a) *(it + a) = data##a;

  DD2(1,2)    SWAP(1, 2)
  DD2(4,5)    SWAP(4, 5)
  DD1(0)      SWAP(0, 2)
  DD1(3)      SWAP(3, 5)
  SWAP(0, 1)  SWAP(3, 4)
  SWAP(1, 4)  SWAP(0, 3)   CB(0)
  SWAP(2, 5)  CB(5)
  SWAP(1, 3)  CB(1)
  SWAP(2, 4)  CB(4)
  SWAP(2, 3)  CB(2)        CB(3)
#undef CB
#undef DD2
#undef DD1
#undef SWAP
}

Обратите внимание, что определение SWAP() следующим образом иногда приводит к несколько лучшей производительности, хотя в большинстве случаев это приводит к несколько меньшей производительности или незначительной разнице в производительности.

#define SWAP(x,y) { const auto a = MIN(data##x, data##y); \
  data##y = MAX(data##x, data##y); \
  data##x = a; }

Если вы просто хотите алгоритм сортировки, который gcc -O3 последовательно хорошо оптимизирует, то, в зависимости от того, как вы передаете ввод, попробуйте один из следующих двух алгоритмов:

template<class T> inline void sort6(T it) {
#define SORT2(x,y) {if(data##x>data##y){auto a=std::move(data##y);data##y=std::move(data##x);data##x=std::move(a);}}
#define DD1(a)   register auto data##a=*(it+a);
#define DD2(a,b) register auto data##a=*(it+a);register auto data##b=*(it+b);
#define CB1(a)   *(it+a)=data##a;
#define CB2(a,b) *(it+a)=data##a;*(it+b)=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

Или (он отличается от вышеупомянутого в первых 5 строках)

template<class T> inline void sort6(T& e0, T& e1, T& e2, T& e3, T& e4, T& e5) {
#define SORT2(x,y) {if(data##x>data##y)std::swap(data##x,data##y);}
#define DD1(a)   register auto data##a=e##a;
#define DD2(a,b) register auto data##a=e##a;register auto data##b=e##b;
#define CB1(a)   e##a=data##a;
#define CB2(a,b) e##a=data##a;e##b=data##b;
  DD2(1,2) SORT2(1,2)
  DD2(4,5) SORT2(4,5)
  DD1(0)   SORT2(0,2)
  DD1(3)   SORT2(3,5)
  SORT2(0,1) SORT2(3,4) SORT2(2,5) CB1(5)
  SORT2(1,4) SORT2(0,3) CB1(0)
  SORT2(2,4) CB1(4)
  SORT2(1,3) CB1(1)
  SORT2(2,3) CB2(2,3)
#undef CB1
#undef CB2
#undef DD1
#undef DD2
#undef SORT2
}

Причина использования ключевого слова register заключается в том, что это один из тех немногих случаев, когда вы знаете, что эти значения нужны в регистрах. Без register компилятор будет понимать это большую часть времени, но иногда это не так. Использование ключевого слова register помогает решить эту проблему. Обычно, однако, не используйте ключевое слово register так как оно скорее замедлит ваш код, чем ускорит его.

Также обратите внимание на использование шаблонов. Это сделано специально, поскольку даже при использовании ключевого слова inline функции шаблонов, как правило, гораздо более агрессивно оптимизируются с помощью gcc, чем функции vanilla C (это связано с тем, что gcc приходится иметь дело с указателями функций для функций vanilla C, но не с функциями шаблонов).,

Ответ 18

Я знаю, что это старый вопрос.

Но я просто написал другое решение, которое хочу поделиться. Используя ничего, кроме вложенного MIN MAX,

Это не быстро, так как он использует 114 из каждого,
мог бы уменьшить его до 75, просто так вот → pastebin

Но тогда это не чисто минмакс больше.

Что может работать min/max для нескольких целых чисел одновременно с AVX

Ссылка PMINSW

#include <stdio.h>

static __inline__ int MIN(int a, int b){
int result =a;
__asm__ ("pminsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ int MAX(int a, int b){
int result = a;
__asm__ ("pmaxsw %1, %0" : "+x" (result) : "x" (b));
return result;
}
static __inline__ unsigned long long rdtsc(void){
  unsigned long long int x;
__asm__ volatile (".byte 0x0f, 0x31" :
  "=A" (x));
  return x;
}

#define MIN3(a, b, c) (MIN(MIN(a,b),c))
#define MIN4(a, b, c, d) (MIN(MIN(a,b),MIN(c,d)))

static __inline__ void sort6(int * in) {
  const int A=in[0], B=in[1], C=in[2], D=in[3], E=in[4], F=in[5];

  in[0] = MIN( MIN4(A,B,C,D),MIN(E,F) );

  const int
  AB = MAX(A, B),
  AC = MAX(A, C),
  AD = MAX(A, D),
  AE = MAX(A, E),
  AF = MAX(A, F),
  BC = MAX(B, C),
  BD = MAX(B, D),
  BE = MAX(B, E),
  BF = MAX(B, F),
  CD = MAX(C, D),
  CE = MAX(C, E),
  CF = MAX(C, F),
  DE = MAX(D, E),
  DF = MAX(D, F),
  EF = MAX(E, F);

  in[1] = MIN4 (
  MIN4( AB, AC, AD, AE ),
  MIN4( AF, BC, BD, BE ),
  MIN4( BF, CD, CE, CF ),
  MIN3( DE, DF, EF)
  );

  const int
  ABC = MAX(AB,C),
  ABD = MAX(AB,D),
  ABE = MAX(AB,E),
  ABF = MAX(AB,F),
  ACD = MAX(AC,D),
  ACE = MAX(AC,E),
  ACF = MAX(AC,F),
  ADE = MAX(AD,E),
  ADF = MAX(AD,F),
  AEF = MAX(AE,F),
  BCD = MAX(BC,D),
  BCE = MAX(BC,E),
  BCF = MAX(BC,F),
  BDE = MAX(BD,E),
  BDF = MAX(BD,F),
  BEF = MAX(BE,F),
  CDE = MAX(CD,E),
  CDF = MAX(CD,F),
  CEF = MAX(CE,F),
  DEF = MAX(DE,F);

  in[2] = MIN( MIN4 (
  MIN4( ABC, ABD, ABE, ABF ),
  MIN4( ACD, ACE, ACF, ADE ),
  MIN4( ADF, AEF, BCD, BCE ),
  MIN4( BCF, BDE, BDF, BEF )),
  MIN4( CDE, CDF, CEF, DEF )
  );


  const int
  ABCD = MAX(ABC,D),
  ABCE = MAX(ABC,E),
  ABCF = MAX(ABC,F),
  ABDE = MAX(ABD,E),
  ABDF = MAX(ABD,F),
  ABEF = MAX(ABE,F),
  ACDE = MAX(ACD,E),
  ACDF = MAX(ACD,F),
  ACEF = MAX(ACE,F),
  ADEF = MAX(ADE,F),
  BCDE = MAX(BCD,E),
  BCDF = MAX(BCD,F),
  BCEF = MAX(BCE,F),
  BDEF = MAX(BDE,F),
  CDEF = MAX(CDE,F);

  in[3] = MIN4 (
  MIN4( ABCD, ABCE, ABCF, ABDE ),
  MIN4( ABDF, ABEF, ACDE, ACDF ),
  MIN4( ACEF, ADEF, BCDE, BCDF ),
  MIN3( BCEF, BDEF, CDEF )
  );

  const int
  ABCDE= MAX(ABCD,E),
  ABCDF= MAX(ABCD,F),
  ABCEF= MAX(ABCE,F),
  ABDEF= MAX(ABDE,F),
  ACDEF= MAX(ACDE,F),
  BCDEF= MAX(BCDE,F);

  in[4]= MIN (
  MIN4( ABCDE, ABCDF, ABCEF, ABDEF ),
  MIN ( ACDEF, BCDEF )
  );

  in[5] = MAX(ABCDE,F);
}

int main(int argc, char ** argv) {
  int d[6][6] = {
    {1, 2, 3, 4, 5, 6},
    {6, 5, 4, 3, 2, 1},
    {100, 2, 300, 4, 500, 6},
    {100, 2, 3, 4, 500, 6},
    {1, 200, 3, 4, 5, 600},
    {1, 1, 2, 1, 2, 1}
  };

  unsigned long long cycles = rdtsc();
  for (int i = 0; i < 6; i++) {
    sort6(d[i]);
  }
  cycles = rdtsc() - cycles;
  printf("Time is %d\n", (unsigned)cycles);

  for (int i = 0; i < 6; i++) {
    printf("d%d : %d %d %d %d %d %d\n", i,
     d[i][0], d[i][1], d[i][2],
     d[i][3], d[i][4], d[i][5]);
  }
}

EDIT:
Ранжирование заказа, вдохновленное Рексом Керром, Гораздо быстрее, чем беспорядок выше

static void sort6(int *o) {
const int 
A=o[0],B=o[1],C=o[2],D=o[3],E=o[4],F=o[5];
const unsigned char
AB = A>B, AC = A>C, AD = A>D, AE = A>E,
          BC = B>C, BD = B>D, BE = B>E,
                    CD = C>D, CE = C>E,
                              DE = D>E,
a =          AB + AC + AD + AE + (A>F),
b = 1 - AB      + BC + BD + BE + (B>F),
c = 2 - AC - BC      + CD + CE + (C>F),
d = 3 - AD - BD - CD      + DE + (D>F),
e = 4 - AE - BE - CE - DE      + (E>F);
o[a]=A; o[b]=B; o[c]=C; o[d]=D; o[e]=E;
o[15-a-b-c-d-e]=F;
}

Ответ 19

Попробуйте сортировать отсортированный список.:) Используйте два массива. Самый быстрый для малого и большого массива.
Если вы соглашаетесь, вы только проверяете, где вставить. Другие большие значения, которые вам не нужно сравнивать (cmp = a-b > 0).
Для 4 чисел вы можете использовать систему 4-5 cmp (~ 4.6) или 3-6 cmp (~ 4.9). Сортировка пузырьков составляет 6 см (6). Много cmp для больших чисел медленнее кода.
Этот код использует 5 cmp (не сортировка MSL):
if (cmp(arr[n][i+0],arr[n][i+1])>0) {swap(n,i+0,i+1);} if (cmp(arr[n][i+2],arr[n][i+3])>0) {swap(n,i+2,i+3);} if (cmp(arr[n][i+0],arr[n][i+2])>0) {swap(n,i+0,i+2);} if (cmp(arr[n][i+1],arr[n][i+3])>0) {swap(n,i+1,i+3);} if (cmp(arr[n][i+1],arr[n][i+2])>0) {swap(n,i+1,i+2);}

Основной MSL 9 8 7 6 5 4 3 2 1 0 89 67 45 23 01 ... concat two sorted lists, list length = 1 6789 2345 01 ... concat two sorted lists, list length = 2 23456789 01 ... concat two sorted lists, list length = 4 0123456789 ... concat two sorted lists, list length = 8

js code

function sortListMerge_2a(cmp)	
{
var step, stepmax, tmp, a,b,c, i,j,k, m,n, cycles;
var start = 0;
var end   = arr_count;
//var str = '';
cycles = 0;
if (end>3)
	{
	stepmax = ((end - start + 1) >> 1) << 1;
	m = 1;
	n = 2;
	for (step=1;step<stepmax;step<<=1)	//bounds 1-1, 2-2, 4-4, 8-8...
		{
		a = start;
		while (a<end)
			{
			b = a + step;
			c = a + step + step;
			b = b<end ? b : end;
			c = c<end ? c : end;
			i = a;
			j = b;
			k = i;
			while (i<b && j<c)
				{
				if (cmp(arr[m][i],arr[m][j])>0)
					{arr[n][k] = arr[m][j]; j++; k++;}
				else	{arr[n][k] = arr[m][i]; i++; k++;}
				}
			while (i<b)
				{arr[n][k] = arr[m][i]; i++; k++;
}
			while (j<c)
				{arr[n][k] = arr[m][j]; j++; k++;
}
			a = c;
			}
		tmp = m; m = n; n = tmp;
		}
	return m;
	}
else
	{
	// sort 3 items
	sort10(cmp);
	return m;
	}
}

Ответ 20

Сортировка 4 элементов с использованием cmp == 0. Числа cmp ~ 4.34 (FF native имеют ~ 4.52), но занимают 3 раза, чем слияние списков. Но лучше, чем CMP операций, если у вас большие цифры или большой текст. Изменить: исправлена ​​ошибка

Онлайн-тест http://mlich.zam.slu.cz/js-sort/x-sort-x2.htm

function sort4DG(cmp,start,end,n) // sort 4
{
var n     = typeof(n)    !=='undefined' ? n   : 1;
var cmp   = typeof(cmp)  !=='undefined' ? cmp   : sortCompare2;
var start = typeof(start)!=='undefined' ? start : 0;
var end   = typeof(end)  !=='undefined' ? end   : arr[n].length;
var count = end - start;
var pos = -1;
var i = start;
var cc = [];
// stabilni?
cc[01] = cmp(arr[n][i+0],arr[n][i+1]);
cc[23] = cmp(arr[n][i+2],arr[n][i+3]);
if (cc[01]>0) {swap(n,i+0,i+1);}
if (cc[23]>0) {swap(n,i+2,i+3);}
cc[12] = cmp(arr[n][i+1],arr[n][i+2]);
if (!(cc[12]>0)) {return n;}
cc[02] = cc[01]==0 ? cc[12] : cmp(arr[n][i+0],arr[n][i+2]);
if (cc[02]>0)
    {
    swap(n,i+1,i+2); swap(n,i+0,i+1); // bubble last to top
    cc[13] = cc[23]==0 ? cc[12] : cmp(arr[n][i+1],arr[n][i+3]);
    if (cc[13]>0)
        {
        swap(n,i+2,i+3); swap(n,i+1,i+2); // bubble
        return n;
        }
    else    {
    cc[23] = cc[23]==0 ? cc[12] : (cc[01]==0 ? cc[30] : cmp(arr[n][i+2],arr[n][i+3]));  // new cc23 | c03 //repaired
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    }
else    {
    if (cc[12]>0)
        {
        swap(n,i+1,i+2);
        cc[23] = cc[23]==0 ? cc[12] : cmp(arr[n][i+2],arr[n][i+3]); // new cc23
        if (cc[23]>0)
            {
            swap(n,i+2,i+3);
            return n;
            }
        return n;
        }
    else    {
        return n;
        }
    }
return n;
}

Ответ 21

Возможно, я опаздываю на вечеринку, но, по крайней мере, мой вклад - это новый подход.

  • Код действительно должен быть встроен
  • даже если в строке указано слишком много ветвей
  • анализирующая часть в основном O (N (N-1)), которая кажется ОК для N = 6
  • код может быть более эффективным, если стоимость swap будет выше (irt стоимость compare)
  • Я полагаюсь на встроенные статические функции.
  • Метод связан с сортировкой ранга
    • вместо рангов используются относительные ранги (смещения).
    • сумма рангов равна нулю для каждого цикла в любой группе подстановок.
    • вместо SWAP() двух элементов, циклы преследуются, требуется только одна временная и одна (register- > register) swap (new < - old).

Обновление: немного изменил код, некоторые люди используют компиляторы С++ для компиляции кода C...

#include <stdio.h>

#if WANT_CHAR
typedef signed char Dif;
#else
typedef signed int Dif;
#endif

static int walksort (int *arr, int cnt);
static void countdifs (int *arr, Dif *dif, int cnt);
static void calcranks(int *arr, Dif *dif);

int wsort6(int *arr);

void do_print_a(char *msg, int *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", *arr);
        }
fprintf(stderr,"\n");
}

void do_print_d(char *msg, Dif *arr, unsigned cnt)
{
fprintf(stderr,"%s:", msg);
for (; cnt--; arr++) {
        fprintf(stderr, " %3d", (int) *arr);
        }
fprintf(stderr,"\n");
}

static void inline countdifs (int *arr, Dif *dif, int cnt)
{
int top, bot;

for (top = 0; top < cnt; top++ ) {
        for (bot = 0; bot < top; bot++ ) {
                if (arr[top] < arr[bot]) { dif[top]--; dif[bot]++; }
                }
        }
return ;
}
        /* Copied from RexKerr ... */
static void inline calcranks(int *arr, Dif *dif){

dif[0] =     (arr[0]>arr[1])+(arr[0]>arr[2])+(arr[0]>arr[3])+(arr[0]>arr[4])+(arr[0]>arr[5]);
dif[1] = -1+ (arr[1]>=arr[0])+(arr[1]>arr[2])+(arr[1]>arr[3])+(arr[1]>arr[4])+(arr[1]>arr[5]);
dif[2] = -2+ (arr[2]>=arr[0])+(arr[2]>=arr[1])+(arr[2]>arr[3])+(arr[2]>arr[4])+(arr[2]>arr[5]);
dif[3] = -3+ (arr[3]>=arr[0])+(arr[3]>=arr[1])+(arr[3]>=arr[2])+(arr[3]>arr[4])+(arr[3]>arr[5]);
dif[4] = -4+ (arr[4]>=arr[0])+(arr[4]>=arr[1])+(arr[4]>=arr[2])+(arr[4]>=arr[3])+(arr[4]>arr[5]);
dif[5] = -(dif[0]+dif[1]+dif[2]+dif[3]+dif[4]);
}

static int walksort (int *arr, int cnt)
{
int idx, src,dst, nswap;

Dif difs[cnt];

#if WANT_REXK
calcranks(arr, difs);
#else
for (idx=0; idx < cnt; idx++) difs[idx] =0;
countdifs(arr, difs, cnt);
#endif
calcranks(arr, difs);

#define DUMP_IT 0
#if DUMP_IT
do_print_d("ISteps ", difs, cnt);
#endif

nswap = 0;
for (idx=0; idx < cnt; idx++) {
        int newval;
        int step,cyc;
        if ( !difs[idx] ) continue;
        newval = arr[idx];
        cyc = 0;
        src = idx;
        do      {
                int oldval;
                step = difs[src];
                difs[src] =0;
                dst = src + step;
                cyc += step ;
                if(dst == idx+1)idx=dst;
                oldval = arr[dst];
#if (DUMP_IT&1)
                fprintf(stderr, "[Nswap=%d] Cyc=%d Step=%2d Idx=%d  Old=%2d New=%2d #### Src=%d Dst=%d[%2d]->%2d <-- %d\n##\n"
                        , nswap, cyc, step, idx, oldval, newval
                        , src, dst, difs[dst], arr[dst]
                        , newval  );
                do_print_a("Array ", arr, cnt);
                do_print_d("Steps ", difs, cnt);
#endif

                arr[dst] = newval;
                newval = oldval;
                nswap++;
                src = dst;
                } while( cyc);
        }

return nswap;
}
/*************/
int wsort6(int *arr)
{
return walksort(arr, 6);
}

Ответ 22

//Bruteforce compute unrolled count dumbsort(min to 0-index)
void bcudc_sort6(int* a)
{
    int t[6] = {0};
    int r1,r2;

    r1=0;
    r1 += (a[0] > a[1]);
    r1 += (a[0] > a[2]);
    r1 += (a[0] > a[3]);
    r1 += (a[0] > a[4]);
    r1 += (a[0] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[0];

    r2=0;
    r2 += (a[1] > a[0]);
    r2 += (a[1] > a[2]);
    r2 += (a[1] > a[3]);
    r2 += (a[1] > a[4]);
    r2 += (a[1] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[1];

    r1=0;
    r1 += (a[2] > a[0]);
    r1 += (a[2] > a[1]);
    r1 += (a[2] > a[3]);
    r1 += (a[2] > a[4]);
    r1 += (a[2] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[2];

    r2=0;
    r2 += (a[3] > a[0]);
    r2 += (a[3] > a[1]);
    r2 += (a[3] > a[2]);
    r2 += (a[3] > a[4]);
    r2 += (a[3] > a[5]);
    while(t[r2]){r2++;} 
    t[r2] = a[3];

    r1=0;
    r1 += (a[4] > a[0]);
    r1 += (a[4] > a[1]);
    r1 += (a[4] > a[2]);
    r1 += (a[4] > a[3]);
    r1 += (a[4] > a[5]);
    while(t[r1]){r1++;}
    t[r1] = a[4];

    r2=0;
    r2 += (a[5] > a[0]);
    r2 += (a[5] > a[1]);
    r2 += (a[5] > a[2]);
    r2 += (a[5] > a[3]);
    r2 += (a[5] > a[4]);
    while(t[r2]){r2++;} 
    t[r2] = a[5];

    a[0]=t[0];
    a[1]=t[1];
    a[2]=t[2];
    a[3]=t[3];
    a[4]=t[4];
    a[5]=t[5];
}

static __inline__ void sort6(int* a)
{
    #define wire(x,y); t = a[x] ^ a[y] ^ ( (a[x] ^ a[y]) & -(a[x] < a[y]) ); a[x] = a[x] ^ t; a[y] = a[y] ^ t;
    register int t;

    wire( 0, 1); wire( 2, 3); wire( 4, 5);
    wire( 3, 5); wire( 0, 2); wire( 1, 4);
    wire( 4, 5); wire( 2, 3); wire( 0, 1); 
    wire( 3, 4); wire( 1, 2); 
    wire( 2, 3);

    #undef wire
}

Ответ 23

Ну, если это всего 6 элементов, и вы можете использовать parallelism, хотите минимизировать условное разветвление и т.д. Почему вы не генерируете все комбинации и не проверяете порядок? Я бы рискнул, что в некоторых архитектурах это может быть довольно быстро (до тех пор, пока у вас есть память prealocated)

Ответ 24

Вот три типичных метода сортировки, которые представляют три разных класса алгоритмов сортировки:

Insertion Sort: Θ(n^2)

Heap Sort: Θ(n log n)

Count Sort: Θ(3n)

Но зайдите обсуждение Стефана Нельсона о самом быстром алгоритме сортировки?, где он обсуждает решение, которое переходит на O(n log log n).. проверьте его реализация в C

Этот алгоритм полулинейной сортировки был представлен в 1995 году:

а. Андерссон, Т. Хагеруп, С. Нильссон и Р. Раман. Сортировка по линейному время? В материалах 27-го ежегодного симпозиума ACM по теории Вычисление, страницы 427-436, 1995.