Производительность QuickSort и MergeSort при последовательных данных, подходящих для памяти и медленных доступов Последовательные данные на диске

Следующая цитата из "Сравнение с другими алгоритмами сортировки"  раздел из Страница сортировки Wikipedia Сортировка

В типичных современных архитектурах эффективные реализации quicksort как правило, превосходят mergesort для сортировки массивов на основе RAM. [цитата] необходимо] С другой стороны, сортировка слияния является стабильной сортировкой и более эффективно при работе с медленными доступными последовательными средами.

Мои вопросы:

  • Почему Quicksort превосходит Mergesort, когда данные, которые нужно отсортировать, могут вписаться в память? Если все данные, необходимые для кэширования или в памяти, не будут быстрыми для доступа к Quicksort и Mergesort?

  • Почему Mergesort более эффективен при обработке медленных доступа к последовательным данным (например, с диска в случае, когда данные, которые должны быть отсортированы, не могут вписаться в память)?

  • (переход от моих комментариев ниже сюда) В массиве arr примитивов (данные являются последовательными) из n элементов. Пара элементов, которые нужно читать и сравнивать в MergeSort, это arr[0] и arr[n/2] (происходит в конечном объединении). Теперь подумайте, что пара элементов, которые нужно читать и сравнивать в QuickSort, это arr[1] и arr[n] (происходит в первом разделе, предположим, что мы поменяем случайным образом выбранный стержень с первым элементом). Мы знаем, что данные считываются в блоках и загружаются в кеш или диск в память (исправьте меня, если я ошибаюсь), разве нет ли лучшего шанса на то, что необходимые данные будут загружаться вместе в одном блоке при использовании MergeSort? Мне кажется, MergeSort всегда будет иметь верх, потому что это, вероятно, сравнение элементов, которые ближе друг к другу. Я знаю, что это False (см. График ниже), потому что QuickSort явно быстрее...... Я знаю, что MergeSort не работает и требует дополнительной памяти, и это, вероятно, замедлит работу. Кроме того, какие части я теряю в своем анализе?

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

изображения из Принстон CS MergeSort и слайды QuickSort


Мой мотив:

Я хочу понять эти концепции, потому что они являются одной из основных причин того, почему mergeSort предпочтительнее при сортировке LinkedList, или нет последовательных данных и quickSort предпочтительнее при сортировке массива или последовательных данных. И почему mergeSort используется для сортировки Object в Java, а quickSort используется для сортировки примитивного типа в java.

: API Java 7 фактически использует TimSort для сортировки объекта, который является гибридом MergeSort и InsertionSort. Для примитивов Double-Pivot QuickSort. Эти изменения были реализованы, начиная с Java SE 7. Это связано с устойчивостью алгоритма сортировки. Почему метод Arrays.sort Java использует два разных алгоритма сортировки для разных типов?


Edit:

Я по достоинству оценю ответ, который касается следующих аспектов:

  • Я знаю, что два алгоритма сортировки различаются по количеству ходов, чтению и сравнениям. Если эти причины способствуют поведению, которое я вижу в моих ответах (я подозревал это), то подробное объяснение того, как будут выполняться шаги и процесс алгоритма сортировки, которые имеют преимущества или недостатки в поисках данных с диска или памяти, будут высоко оценены,
  • Примеры приветствуются. Я лучше разбираюсь в примерах.

Примечание: если вы читаете ответ @rcgldr. ознакомьтесь с нашей беседой в чате, в ней есть много хороших объяснений и подробностей. https://chat.stackoverflow.com/rooms/161554/discussion-between-rcgldr-and-oliver-koo

Ответы

Ответ 1

Основное отличие состоит в том, что сортировка слияния делает больше ходов, но меньше сравнений, чем быстрый сортировка. Даже в случае сортировки массива родных типов быстрая сортировка только на 15% быстрее, по крайней мере, когда я протестировал ее на больших массивах псевдослучайных 64-битных целых без знака, которые должны быть быстрыми, лучше всего, на моем (Intel 3770K 3.5ghz, Windows 7 Pro 64 бит, Visual Studio 2015, сортировка 16 миллионов псевдослучайных 64-битных целых без знака, 1,32 секунды для быстрой сортировки, 1,55 секунды для сортировки слияния, 1,32/1,55 ~ = 0,85, поэтому быстрая сортировка была примерно на 15% быстрее, чем сортировка слияния). Мой тест был с быстрой сортировкой, у которой не было проверок, чтобы избежать наихудшего случая O (n ^ 2) или O (n). Поскольку проверки добавляются к быстрому сортировке, чтобы уменьшить или предотвратить поведение в худшем случае (например, вернуться к сортировке кучи, если рекурсия становится слишком глубокой), преимущество скорости уменьшается до менее 10% (что является разницей между реализацией VS2015 std:: sort (изменение быстрого сортировки) по сравнению с std:: stable_sort (измененная сортировка слияния).

Если сортировка "строк", более вероятно, что то, что сортируется, представляет собой массив указателей (или ссылок) на эти строки. В этом случае сортировка слияния выполняется быстрее, потому что ходы включают указатели, тогда как сравнения включают уровень косвенности и сравнение строк.

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

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


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

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


При сортировке массива собственных типов на процессоре с 16 регистрами, таких как X86 в режиме 64 бит, 8 регистров, используемых в качестве начальных + конечных указателей (или ссылок) для 4 сеансов, затем с 4-сторонним слиянием сортировка часто примерно такая же или немного быстрее, чем быстрая сортировка, предполагая, что компилятор оптимизирует указатели или ссылки для регистрации. Это аналогичный компромисс, как и быстрая сортировка, сортировка с 4-сторонним слиянием больше сравнивает (1,5 x сравнивает), но меньше ходов (0.5 x перемещается), чем традиционная сортировка с двумя способами.


Следует отметить, что эти сорта связаны cpu, а не связаны с памятью. Я сделал многопоточную версию сортировки слияния снизу вверх, а в случае использования 4 потоков сортировка была в 3 раза быстрее. Ссылка на пример кода Windows с использованием 4 потоков:

https://codereview.stackexchange.com/info/148025/multithreaded-bottom-up-merge-sort