Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Алгоритмы > Мультипоточная реализация quicksort


Автор: Coder 5.3.2009, 03:28
Пытаюсь сделать мультипоточную быструю сортировку. Основная загвоздка вот в чем:
Допустим, на вход поступает массив: 2, 6, 8, 4, 0, 3, 7, 4. Одному потоку достаются элементы [0..3] другому [4..7]. На выходе от потоков получаем два отсортированных массива: 2, 4, 6, 8 и 0, 3, 4, 7. Ну а здесь получается нужно еще как-то эти два массива слить (фактически заново отсортировать), что нарушает условие задачи.

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

Автор: Lipetsk 5.3.2009, 09:06
заново сортировать не надо, вам осталось получать минимальные элементы из двух очередей

Автор: Coder 5.3.2009, 09:20
Lipetsk, я немного не уточнил - число потоков не обязательно равно 2, может быть и больше (этот параметр задается) => подмассивов будет больше. 

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


Автор: maxim1000 5.3.2009, 14:56
метод quicksort предполагает несколько другую последовательность действий:
сначала мы разделяем массив на две части так, что любое число из первой части меньше любого числа из второй части, поэтому сразу после сортировки этих частей массив получается отсортированным
как вариант, для двух потоков можно первое разделение провести в однопоточном режиме, а уже отдельные части сортировать в разных потоках

Автор: Silent 5.3.2009, 20:58
Первый проход будет в однопоточном режиме, массив отсортируется относительно среднего элемента. Потом запускаем по потоку на правую и левую часть, и так рекурсивно, то есть один поток после упорядочивания создает два дочерних потока и завершается. Можно ввести ограничение на количество одновременно выполняемых потоков.

Автор: Coder 6.3.2009, 10:25
Вот что у меня получилось.

Код

#include <stdlib.h>
#include <windows.h>
#include <iostream>
#include <time.h>

using namespace std;

// массив для сортировки
int *super_array;

// структура для передачи параметров потоку
struct param_RL{
    int R, L;
};

CRITICAL_SECTION cs;
CRITICAL_SECTION inc_cs;

int ThreadCount = 0;        // содержит текущее число потоков
int MaxThreads = 2;            // максимальное число потоков

void m_qsort(int l, int r);

DWORD WINAPI thread_sort(LPVOID lpParameter){
    param_RL *p = (param_RL*)lpParameter;

    // сортируем в потоке
    m_qsort(p->L,p->R);

    delete p;

    // выходим, уменьшаем счетчик потоков
    EnterCriticalSection(&inc_cs);
    ThreadCount--;
    LeaveCriticalSection(&inc_cs);

    ExitThread(0);
    return 0;
}

void m_qsort(int l, int r){
    int i = l;
    int j = r;
    int x = super_array[(r + l) /2];
    do  {
        while(super_array[i]<x)
            i++;
        while(super_array[j]>x)
            j--;
        if (i<=j){
            EnterCriticalSection(&cs);
            swap(super_array[i++], super_array[j--]);
            LeaveCriticalSection(&cs);
        }
    }while(i<=j);
 
    if (i < r){
        // выбираем метод запуска для сортировки новой части
        if (ThreadCount>=MaxThreads){
            m_qsort(i,r);    // вызываем рекурсию внутри потока
        }
        else{    // создаем новый поток
            EnterCriticalSection(&inc_cs);
            ThreadCount++;
            LeaveCriticalSection(&inc_cs);

            param_RL *p = new param_RL;
            p->L=i;
            p->R=r;
            CreateThread(NULL,0,thread_sort,(PVOID)p,0,0);
        }
    }

    if (l < j){
        // выбираем метод запуска для сортировки новой части
        if (ThreadCount>=MaxThreads){
            m_qsort(l,j);
        }
        else{
            EnterCriticalSection(&inc_cs);
            ThreadCount++;
            LeaveCriticalSection(&inc_cs);
            
            param_RL *p = new param_RL;
            p->L=l;
            p->R=j;
            CreateThread(NULL,0,thread_sort,(PVOID)p,0,0);
        }
    
    }
}

void main(){
    InitializeCriticalSection(&cs);
    InitializeCriticalSection(&inc_cs);

    int n = 1000000;    // число элементов
    long s1, s2;        // для замера времени

    super_array=new int[n];
    srand((unsigned)time(NULL));
    for (int i=0; i<n; i++){
        super_array[i]=rand();
//        cout<<super_array[i]<<" ";
    }

    s1=GetTickCount();

    // запускаем сортировку
    m_qsort(0,n-1);
    
    // ждем завершения всех потоков
    while (true){
        EnterCriticalSection(&inc_cs);
        if (!ThreadCount){    
            LeaveCriticalSection(&inc_cs);
            break;
        }
        LeaveCriticalSection(&inc_cs);
    }
    s2=GetTickCount();

    //cout<<endl<<endl;
    //for (int i=0; i<n; i++){
    //    cout<<super_array[i]<<" ";
    //}
    cout<<endl<<endl<<s2-s1<<" ms.";

    delete []super_array;

    cin.get();    
    
    DeleteCriticalSection(&cs);
    DeleteCriticalSection(&inc_cs);
}


В общем схема такая, как расписал Silent. 
Цитата

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


Все вроде правильно - верно сортируется массив, работает заданное число потоков. Но вот что я не пойму - почему при чистой рекурсии (если MaxThreads=0) массив сортируется  быстрее (загружая на 50% процессор), хотя 2 поточная версия использует каждое ядро и грузит проц на 100%...

Вот данные замеров (число элементов = 10`000`000):
Число спец. потоков для сортировки  / время в миллисек.
0 / 13000
2 / 15203
4 / 49250

Вот такая картина. По идее на моем 2-ядерном процессоре, должна быстрее работать версия, которая используется на всю два ядра. Но почему-то выигрывает чисто рекурсивная версия...

Есть мысли? 

Автор: dumb 7.3.2009, 03:47
Цитата(Coder @  6.3.2009,  10:25 Найти цитируемый пост)
почему при чистой рекурсии (если MaxThreads=0) массив сортируется  быстрее
после разделения работы между потоками никаких блокировок в рабочих циклах быть не должно.

Автор: Coder 7.3.2009, 05:06
Цитата(dumb @ 7.3.2009,  11:47)
Цитата(Coder @  6.3.2009,  10:25 Найти цитируемый пост)
почему при чистой рекурсии (если MaxThreads=0) массив сортируется  быстрее
после разделения работы между потоками никаких блокировок в рабочих циклах быть не должно.

Действительно! У нас ведь уже определены границы массива внутри одного потока и они не могут нарушиться. Закомментировал крит. секцию cs - теперь действительно поточная версия сортирует где-то на 1.3 сек. быстрее чисто рекурсивной. Да и вообще появился общий выигрыш во времени.

Еще думаю оптимизировать один кусок, путем создания массива хендлов потоков и функции WaitForMultipleObjects(). Возможно она не так будет грузить проц, как вот эта проверка:

Код

    // ждем завершения всех потоков
    while (true){
        EnterCriticalSection(&inc_cs);
        if (!ThreadCount){    
            LeaveCriticalSection(&inc_cs);
            break;
        }
        LeaveCriticalSection(&inc_cs);
    }



Автор: maxim1000 7.3.2009, 10:55
кроме того, стоит подумать о накладных расходах
конечно, если мы создаём поток для того, чтобы отсортировать 10000000 элементов, скорее всего, станет быстрее
однако, для 10элементов я бы ужене был так уверен

создание потока - нетривиальное действие, которое затрагивуает обращение к системе, создание там каких-то структур и т.д.

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

Автор: Coder 7.3.2009, 11:26
maxim1000, насчет маленьких кусочков - в точку. Нужно будет экспериментально подобрать эту границу.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)