Я работаю над библиотекой распараллеливания для языка программирования D. Теперь, когда я очень доволен базовыми примитивами (параллельными foreach, map, reduce и задачами/фьючерсами), я начинаю думать о некоторых параллельных алгоритмах более высокого уровня. Среди наиболее очевидных кандидатов для распараллеливания - сортировка.(Когда) являются параллельными сортами практическими и как вы пишете эффективный?
Мой первый вопрос заключается в параллельных версиях алгоритмов сортировки, полезных в реальном мире, или они в основном академические? Если они полезны, где они полезны? Я лично редко использовал их в своей работе, просто потому, что я обычно привязывал все свои ядра на 100%, используя гораздо более крупный уровень параллелизма, чем один вызов sort().
Во-вторых, кажется, что быстрая сортировка почти неловко параллельна для больших массивов, но я не могу получить почти линейные ускорения, которые, я считаю, должен получить. Для быстрого сортировки единственной неотъемлемой последовательной частью является первый раздел. Я попытался распараллелить быстрый сортировать, после каждого раздела, сортировать два подмассива параллельно. В упрощенном псевдокоде:
// I tweaked this number a bunch. Anything smaller than this and the
// overhead is smaller than the parallelization gains.
const smallestToParallelize = 500;
void quickSort(T)(T[] array) {
if(array.length < someConstant) {
insertionSort(array);
return;
}
size_t pivotPosition = partition(array);
if(array.length >= smallestToParallelize) {
// Sort left subarray in a task pool thread.
auto myTask = taskPool.execute(quickSort(array[0..pivotPosition]));
quickSort(array[pivotPosition + 1..$]);
myTask.workWait();
} else {
// Regular serial quick sort.
quickSort(array[0..pivotPosition]);
quickSort(array[pivotPosition + 1..$]);
}
}
Даже для очень больших массивов, где время первого раздела занимает ничтожно, я могу получить только около 30% ускорения на двухъядерном, по сравнению с чисто серийной версией алгоритма , Я предполагаю, что узким местом является доступ к общей памяти. Любое понимание того, как устранить это узкое место или что еще может быть узким местом?
Редактировать: Мой пул задач имеет фиксированное число потоков, равное количеству ядер в системе минус 1 (так как основной поток также работает). Кроме того, тип ожидания, который я использую, - это ожидание работы, то есть, если задача запущена, но не закончена, поток, вызывающий workWait()
, перехватывает другие задания из пула и делает их до тех пор, пока он не будет ждать. Если задача не запущена, она завершается в текущем потоке. Это означает, что ожидание не является неэффективным. Пока есть работа, все потоки будут заняты.
Я не знаю, как сделать вашу быструю сортировку распараллелить лучше, но есть вариант, называемый samplesort, который по своей природе гораздо быстрее, чем ванильная сортировка, и, насколько я могу видеть, это должны быть одинаково параллельны. –