Реализация Mergesort pThread занимает столько же времени, сколько и однопоточная

0

(Я попытался упростить это, насколько мог, чтобы узнать, где я делаю что-то не так.)

Идея кода заключается в том, что у меня есть глобальный массив * v (надеюсь, что использование этого массива не замедляет работу, потоки никогда не должны иметь одинаковое значение, потому что все они работают на разных диапазонах), и я пытаюсь создать 2 потока каждый из которых сортирует первую половину, соответственно вторую половину, вызывая функцию merge_sort() с соответствующими параметрами.

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


Это (релевантный) код:

//Это 2 функции сортировки, каждый поток вызывает merge_sort (..). Это проблема? оба потока, вызывающие одну и ту же (нормальную) функцию?

void merge (int *v, int start, int middle, int end) {
    //dynamically creates 2 new arrays for the v[start..middle] and v[middle+1..end]
    //copies the original values into the 2 halves
    //then sorts them back into the v array
}

void merge_sort (int *v, int start, int end) {
    //recursively calls merge_sort(start, (start+end)/2) and merge_sort((start+end)/2+1, end) to sort them
    //calls merge(start, middle, end) 
}

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

void* mergesort_t2(void * arg) {
    t_data* th_info = (t_data*)arg;
    merge_sort(v, th_info->a, th_info->b);
    return (void*)0;
}

//в основном я просто создаю 2 потока, вызывающих указанную выше функцию

int main (int argc, char* argv[])
{
    //some stuff

    //getting the clock to calculate run time
    clock_t t_inceput, t_sfarsit;
    t_inceput = clock();

    //ignore crt_depth for this example (in the full code i'm recursively creating new threads and i need this to know when to stop)
    //the a and b are the range of values the created thread will have to sort
    pthread_t thread[2];
    t_data next_info[2];
    next_info[0].crt_depth = 1;
    next_info[0].a = 0;
    next_info[0].b = n/2;
    next_info[1].crt_depth = 1;
    next_info[1].a = n/2+1;
    next_info[1].b = n-1;

    for (int i=0; i<2; i++) {
        if (pthread_create (&thread[i], NULL, &mergesort_t2, &next_info[i]) != 0) {
            cerr<<"error\n;";
            return err;
        }
    }

    for (int i=0; i<2; i++) {
        if (pthread_join(thread[i], &status) != 0) {
            cerr<<"error\n;";
            return err;
        }
    }

    //now i merge the 2 sorted halves
    merge(v, 0, n/2, n-1);

    //calculate end time
    t_sfarsit = clock();

    cout<<"Sort time (s): "<<double(t_sfarsit - t_inceput)/CLOCKS_PER_SEC<<endl;
    delete [] v;
}

Выход (на 1 миллион значений):

Sort time (s): 1.294

Вывод с прямым вызовом merge_sort, без потоков:

Sort time (s): 1.388

Выход (на 10 миллионов значений):

Sort time (s): 12.75

Вывод с прямым вызовом merge_sort, без потоков:

Sort time (s): 13.838

Решение:

Я хотел бы поблагодарить WhozCraig и Адама, поскольку они намекали на это с самого начала.

Я использовал inplace_merge(..) вместо своей собственной, и время выполнения программы такое же, как сейчас.

Здесь моя начальная функция слияния (не совсем уверен, что начальная, я, вероятно, несколько раз ее модифицировал, также индексы массивов могут быть ошибочными прямо сейчас, я пошел туда и обратно между [a, b] и [a, b), это была только последняя прокомментированная версия):

void merge (int *v, int a, int m, int c) { //sorts v[a,m] - v[m+1,c] in v[a,c]

    //create the 2 new arrays
    int *st = new int[m-a+1];
    int *dr = new int[c-m+1];
    //copy the values
    for (int i1 = 0; i1 <= m-a; i1++)
        st[i1] = v[a+i1];
    for (int i2 = 0; i2 <= c-(m+1); i2++)
        dr[i2] = v[m+1+i2];

    //merge them back together in sorted order
    int is=0, id=0;
    for (int i=0; i<=c-a; i++)  {
        if (id+m+1 > c || (a+is <= m && st[is] <= dr[id])) {
            v[a+i] = st[is];
            is++;
        }
        else {
            v[a+i] = dr[id];
            id++;
        }
    }
    delete st, dr;
}

все это было заменено на:

inplace_merge(v+a, v+m, v+c);

Редактируйте несколько раз на моем трехъядерном процессоре 3ghz:

1 миллион значений: 1 поток: 7.236 s 2 потока: 4.622 s 4 потока: 4.692 s

10 миллионов значений: 1 поток: 82.034 s 2 темы: 46.189 s 4 темы: 47.36 s

  • 0
    Ваше merge по-прежнему последовательно. Какая доля времени расходуется на этапах merge_sort и merge ?
  • 0
    Также просто чтобы убедиться, что вы заново изобретаете колесо: cplusplus.com/reference/algorithm/merge
Показать ещё 9 комментариев
Теги:
multithreading
pthreads-win32

2 ответа

0
Лучший ответ

Одна вещь, которая поразила меня: "динамически создает 2 новых массива [...]". Поскольку для обоих потоков потребуется память из системы, им необходимо приобрести замок для этого, что вполне может быть вашим узким местом. В частности, идея выделения микроскопических массивов звучит ужасно неэффективно. Кто-то предложил сортировку на месте, которая не требует дополнительного хранилища, что намного лучше для производительности.

Другое дело, часто забытое стартовое полупредложение для любых измерений сложности большой O: "Существует n0, так что для всех n> n0...". Другими словами, возможно, вы еще не достигли n0? Недавно я увидел видео (надеюсь, кто-то еще его запомнит), где некоторые люди пытались определить этот предел для некоторых алгоритмов, и их результаты заключались в том, что эти ограничения на удивление высоки.

  • 1
    Я как раз собирался опубликовать это, я изменил свою программу для использования inplace_merge вместо моей реализации слияния, и теперь числа выглядят так, как должны.
0

Примечание: поскольку OP использует Windows, мой ответ ниже (который неправильно принял Linux) может не применяться. Я оставил его ради тех, кто мог бы найти полезную информацию.


clock() - неправильный интерфейс для измерения времени в Linux: он измеряет время процессора, используемое программой (см. http://linux.die.net/man/3/clock), которое в случае нескольких потоков является суммой Время процессора для всех потоков. Вам нужно измерить прошедшее время или время разгона. См. Более подробную информацию в этом вопросе SO: C: использование clock() для измерения времени в многопоточных программах, в котором также указано, какой API можно использовать вместо clock().

В реализации на основе MPI, с которой вы пытаетесь сравнить, используются два разных процесса (то, что MPI обычно включает параллелизм), а время процессора второго процесса не включено, поэтому время процессора близко к времени разгона. Тем не менее, все еще неправильно использовать время процессора (и, следовательно, clock()) для измерения производительности даже в последовательных программах; по какой-то причине, если программа ждет, например, сетевого события или сообщения из другого процесса MPI, он по-прежнему проводит время, но не процессорное время.

Обновление. В Microsoft, использующем библиотеку времени выполнения C, clock() возвращает время настенных часов, так что это нормально для использования в ваших целях. Это неясно, если вы используете Microsoft toolchain или что-то еще, например Cygwin или MinGW.

  • 0
    Время с помощью timeGetTime () дает тот же номер. Кроме того, я попытался рассчитать время сам, и это не выглядит как проблема, я думаю, что мой код почему-то не запускает потоки одновременно.
  • 0
    О, вы работаете на Windows? Я был введен в заблуждение использованием pthreads и предположил, что Linux.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню