2012-03-23 3 views
6

Это не алгоритмический вопрос, а вопрос реализации.Есть ли процедура сортировки быстрее, чем qsort?

У меня есть структура данных, которая выглядит как:

struct MyStruct { 
    float val; 
    float val2; 
    int idx; 
} 

Я иду через массив около 40 миллионов элементов, а также назначить поля в «VAL» быть элементом, а поле «IDX» в быть индексом.

Я тогда вызова:

MyStruct* theElements = new MyStruct[totalNum]; 
qsort(theElements, totalNum, sizeof(MyStruct), ValOrdering); 

, а затем, когда я заполняю val2, в обратном порядке с

qsort(theElements, totalNum, sizeof(MyStruct), IndexOrdering); 

где

static int ValOrdering(const void* const v1, const void* const v2) 
{ 
    if (((struct MyStruct*) v1)->val < ((struct MyStruct*) v2)->val) 
    return -1; 

    if (((struct MyStruct*) v1)->val> ((struct MyStruct*) v2)->val) 
    return 1; 

    return 0; 
} 

и

static int IndexOrdering(const void* const v1, const void* const v2) 
{ 
    return ((struct MyStruct*) v1)->idx- ((struct MyStruct*) v2)->idx; 
} 

Эта настройка занимает 4 секунды для выполнения обоих типов. 4 секунды, похоже, долгое время для своего рода 40 миллионов элементов, чтобы взять на себя процессор 3Ghz i5; есть ли более быстрый подход? Я использую vs2010 с компилятором Intel (у него есть сортировки, но не над такими структурами, которые я вижу).

Update: Использование зОго :: рода бреет около 0,4 секунд от среды выполнения, называется как:

std::sort(theElements, theElements + totalPixels, ValOrdering); 
std::sort(theElements, theElements + totalPixels, IndexOrdering); 

и

bool GradientOrdering(const MyStruct& i, const MyStruct& j){ 
    return i.val< j.val; 
} 
bool IndexOrdering(const MyStruct& i, const MyStruct& j){ 
    return i.idx< j.idx; 
} 

добавив 'инлайн' ключевое слово предикаты делает похоже, не имеет значения. Поскольку у меня есть, и спецификация позволяет, четырехъядерную машину, я буду проверять какой-то многопоточный вид.

Update 2: После @SirGeorge и @stark, я взял взгляд на одного сорта сделано с помощью указателя перенаправляет:

bool GradientOrdering(MyStruct* i, MyStruct* j){ 
    return i->val< j->val; 
} 
bool IndexOrdering(MyStruct* i, MyStruct* j){ 
    return i->idx< j->idx; 
} 

Даже если есть только один вызов сортировки (подпрограмме GradientOrdering), полученный алгоритм занимает 5 секунд, на 1 секунду дольше, чем подход qsort. Похоже, std :: sort выигрывает.

Update 3: Похоже, Intel, tbb::parallel_sort является победителем, принимая время выполнения одного сорта до 0,5с на моей системе (так, 1.0с для обоих, что означает, что это довольно хорошо масштабировании из оригинальной версии 4.0 s для обоих). Я попытался пойти с параллельной фантазией, предложенной Microsoft here, но поскольку я уже использую tbb, а синтаксис для parallel_sort идентичен синтаксису для std::sort, я мог бы использовать мои более ранние std::sort компараторы, чтобы все было закончено.

Я также воспользовался предложением @ gbulmer (действительно, с помощью перехвата над головой), что у меня уже есть исходные индексы, поэтому вместо второго сортировки мне просто нужно назначить второй массив с помощью индексы от первого назад в отсортированном порядке. Я могу избавиться от использования этой памяти, потому что я только развертываю на 64-битных машинах с объемом памяти не менее 4 ГБ (хорошо, чтобы эти спецификации работали раньше времени); без этого знания потребуется вторая сортировка.

Предложение @ gbulmer дает наибольшее ускорение, но исходный вопрос задает вопрос о наиболее быстрой сортировке. std::sort является самым быстрым однопоточным, parallel_sort является самым быстрым многопоточным, но никто не ответил на этот вопрос, поэтому я даю @gbulmer чек.

+3

'std :: sort' = больше информации о типе и более встраиваемых возможностей. –

+0

Вы можете попробовать многопоточную сортировку слияния. – manasij7479

+3

Вы знаете что-нибудь о распределении данных? Или это совершенно случайно? –

ответ

3

Набор данных огромен по сравнению с кешем, поэтому он будет ограничен кэшем.

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

Рассмотрите разбиение структуры на две структуры в двух массивах.

В качестве эксперимента, сравните проход 1, с пропуском одной, где структура является лишь { float val; int idx; };

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

Если кэш-память является ключевой проблемой, возможно, стоит рассмотреть многопользовательские слияния или Shell sort; что-то улучшить местность.

Попробуйте отсортировать подмножества кеш-размера записей, затем выполните многопоточные слияния (возможно, стоит посмотреть на спецификацию диспетчера кэша процессора, чтобы выяснить, ясно ли о количестве потоков предварительной выборки, пытается ли ожидать Опять же, уменьшение размера наборов данных за счет уменьшения размера структур, передаваемых из ОЗУ, может быть q победным.

Как генерируется поле idx? Похоже, что это исходное положение в массиве. Является ли это индексом исходной записи?

Если это так, просто выделите второй массив и скопируйте первый на второй:

struct { float val; float val2; int idx } sortedByVal[40000000]; 
struct { float val; float val2 } sortedbyIdx[40000000]; 

for (int i=0; i<40000000; ++i) { 
    sortedbyIdx[sortedByVal[i].idx].val = sortedByVal[i].val; 
    sortedbyIdx[sortedByVal[i].idx].val2 = sortedByVal[i].val2; 
} 

Второго сорта нет. Если это так, объедините выделение значения val2 с этим проходом.

Редактировать

мне было интересно, об относительной производительности, так что я написал программу для сравнения «библиотеки» C Функцию сортировки, QSort, слияние, пирамидальной сортировки, а также сравнить сортировку IDX с копией IDX. Он также повторно сортирует отсортированные значения, чтобы получить некоторую информацию об этом. Это тоже интересно. Я не реализовал и не проверил сортировку Shell, которая часто набирает qsort на практике.

Программа использует параметры командной строки для выбора того, какой тип сортировки, а также для сортировки по idx или просто для копирования. Код: http://pastebin.com/Ckc4ixNp

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

Я запустил это на старом MacBook Pro с тактовой частотой 2,2 ГГц Intel Core 2 Duo. Некоторое время зависит от ОС C.

Timing (немного переформатировать):

qsort(data, number-of-elements=40000000, element-size=12) 
Sorting by val - duration =   16.304194 
Re-order to idx by copying - duration = 2.904821 
Sort in-order data - duration =   2.013237 
Total duration = 21.222251 
User Time:  20.754574 
System Time:  0.402959 

mergesort(data, number-of-elements=40000000, element-size=12) 
Sorting by val - duration =   25.948651 
Re-order to idx by copying - duration = 2.907766 
Sort in-order data - duration =   0.593022 
Total duration = 29.449438 
User Time:  28.428954 
System Time:  0.973349 

heapsort(data, number-of-elements=40000000, element-size=12) 
Sorting by val - duration =   72.236463 
Re-order to idx by copying - duration = 2.899309 
Sort in-order data - duration =  28.619173 
Total duration = 103.754945 
User Time:  103.107129 
System Time:  0.564034 

ПРЕДУПРЕЖДЕНИЕ: Это один работает. Для получения разумной статистики потребуется много прогонов.

Код в pastebin фактически сортирует «уменьшенный размер», 8-байтовый массив. На первом проходе нужны только val и idx, и по мере того, как массив копируется при добавлении val2, в первом массиве нет необходимости в val2. Эта оптимизация заставляет функции сортировки копировать меньшую структуру, а также встраивать больше структур в кеш, которые хороши. Я был разочарован тем, что это дает несколько процентов улучшения qsort. Я интерпретирую это так: qsort быстро получает сортировку блоков до размера, который помещается в кеш.

Та же стратегия уменьшенного размера дает более 25% -ное улучшение на heapsort.

Timing 8 байт структур, без val2:

qsort(data, number-of-elements=40000000, element-size=8) 
Sorting by val - duration =   16.087761 
Re-order to idx by copying - duration = 2.858881 
Sort in-order data - duration =   1.888554 
Total duration = 20.835196 
User Time:  20.417285 
System Time:  0.402756 

mergesort(data, number-of-elements=40000000, element-size=8) 
Sorting by val - duration =   22.590726 
Re-order to idx by copying - duration = 2.860935 
Sort in-order data - duration =   0.577589 
Total duration = 26.029249 
User Time:  25.234369 
System Time:  0.779115 

heapsort(data, number-of-elements=40000000, element-size=8) 
Sorting by val - duration =   52.835870 
Re-order to idx by copying - duration = 2.858543 
Sort in-order data - duration =  24.660178 
Total duration = 80.354592 
User Time:  79.696220 
System Time:  0.549068 

ПРЕДУПРЕЖДЕНИЕ: Это один работает. Для получения разумной статистики потребуется много прогонов.

+0

Выполнение этой трансформации полностью поражает причину ОП в том, чтобы делать вид в первую очередь. Кроме того, эти данные все равно не будут вписываться в кеш, поэтому я подозреваю, что вы собираетесь платить за такие вещи. (Даже 40 миллионов 'int' будет 160MB) –

+0

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

+0

Уменьшение размера данных бесполезно, если оно не решает проблему, которую нужно решить. Это отбрасывает половину данных. –

14

Вообще говоря, C++ 's std::sort расположен в algorithm будет бить qsort, потому что позволяет компилятору оптимизировать расстояние косвенного вызова через указатель на функцию, и делает его более легким для компилятора выполнить встраивание. Однако это будет только постоянный фактор ускорения; qsort уже использует очень быстрый алгоритм сортировки.

Обратите внимание, что если вы решите перейти на std::sort, что ваш функтор сравнения должен будет измениться. std::sort принимает простой, чем сравнение, возвращающий bool, в то время как std::qsort принимает функтор, возвращающий -1, 0 или 1, в зависимости от ввода.

+0

Постоянное ускорение времени в порядке; Я бы предположил, что операции литья O (N log N) не являются бесплатными. Я проверю это, спасибо. – mmr

+0

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

+0

@mmr фактически операции литья не стоят ничего (если не имеют дело с полиморфными типами иногда или типами, которые требуют конверсий, таких как float to int или что-то еще), учитывая, что типы и система типов существуют только во время компиляции –

0

Все алгоритмы сортировки известны и там. Их легко реализовать. Оцените их.

Quick-Sort может быть не самым быстрым во всех случаях, но он довольно эффективен в среднем. Однако 40 миллионов записей много, сортировка которых за 3-4 секунды не является неслыханной.

редактировать

Я резюмировать свои комментарии: Это было доказано, что при Тьюринга (здесь написано верно !!!) модели, алгоритмы сравнения сортировки ограничены Q (п § п). Таким образом, сложный подход к улучшению невелик, но дьявол находится в деталях. Чтобы узнать о различиях в производительности алгоритмов эквивалентной сложности, вам нужно сравнить их и посмотреть на результаты.

Если, однако, у вас есть дополнительные знания о ваших данных (например, idx будет находиться в пределах определенного пресета и относительно небольшого диапазона), вы можете использовать алгоритмы, которые не являются сортировочными, и имеют сложность. Вы должны по-прежнему ориентироваться, чтобы убедиться, что улучшение действительно происходит для ваших данных, но для большого объема разница между Ω (n log n) и Ω (n), вероятно, будет заметной. Примером таких алгоритмов является сортировка ведра.

Для более подробного анализа списка и сложности - начало here.

+4

Как вы можете сказать, что все алгоритмы сортировки известны? –

+4

Все * известные * есть. –

+0

@SethCarnegie фактически доказано, что вы не можете сортировать меньше, чем O (NLogN) на модели гастролей без специального предварительного знания данных (то есть: raw sort), поэтому, даже если есть дополнительные алгоритмы для обнаружения, сложность остается тоже самое. Я хочу сказать, что теперь нужно провести сравнительный анализ, чтобы решить, что будет быстрее для ОП. – littleadv

1

Сейчас вы сортируете array of structures, что означает, что каждый обмен в массиве - как минимум два задания (копирование целых структур). Вы можете попытаться отсортировать массив указателей в структурах, что позволит сэкономить вам много копий (просто указатели копирования), но вы будете использовать больше памяти. Еще одно преимущество сортировки массива указателей состоит в том, что у вас может быть несколько из них (каждый из них отсортирован по-другому) - снова требуется больше памяти. Однако дополнительное указание указателя может быть дорогостоящим. Вы также можете попытаться использовать оба подхода, предложенные здесь другими: std::qsort с массивом указателей - и посмотрите, есть ли у вас ускорение в вашем случае.

+0

Обмен дешевле, но все эти направления дороги. Я подозреваю, что любой метод с указателями будет значительно медленнее, чем то, что было опубликовано OP. –

+0

Для каждого сравнения также требуется дополнительное косвенное использование указателя. Поэтому, хотя я согласен, что это стоит попробовать, это может не помочь и может даже повредить. – Nemo

+0

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

2

При сортировке по индексу radix sort может быть быстрее, чем quicksort. Вероятно, вы захотите сделать это в базе, которая имеет мощность 2 (поэтому вы можете использовать побитовые операции вместо модуля).

+3

+1 - НО: Сорт Radix быстрее асимптотически, но обычно имеет довольно ужасные постоянные факторы в реальных реализациях. Что-то стоит попробовать, но не думайте о том, чтобы избавиться от этого лишнего 'lg n' в качестве гигантской выгоды. Причина в том, что большинство языков программирования не отправляют сортировку radix в свои стандартные библиотеки. –

2

std::sort() должно быть более чем на 10% быстрее. Однако вам понадобятся две вещи:

  1. Использование указателя функции берет героизм из компилятора, чтобы обнаружить, что функция может быть встроена. Функциональный объект со встроенным оператором вызова функции сравнительно прост.
  2. В режиме отладки std::sort() ядро ​​не будет оптимизировано, в то время как qsort() оптимизирован: попробуйте выполнить компиляцию в режиме деблокирования.
Смежные вопросы