(Я попытался максимально упростить это, чтобы выяснить, где я делаю что-то не так.)
Идея кода в том, что у меня есть глобальный массив * 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);
Отредактируйте несколько раз на моем 3-ГГц двухъядерном процессоре:
1 миллион значений:
1 поток: 7,236 с
2 темы: 4.622 с
4 темы: 4.692 с
10 миллионов значений:
1 поток: 82,034 с
2 темы: 46,189 с
4 темы: 47,36 с
Меня поразила одна вещь: «динамически создает 2 новых массива […]». Поскольку обоим потокам потребуется память из системы, им необходимо получить блокировку для этого, что может стать вашим узким местом. В частности, идея выделения микроскопических массивов звучит ужасно неэффективно. Кто-то предложил сортировку на месте, которая не требует дополнительного хранилища, что намного лучше для производительности.
Другое дело — часто забываемое начальное половинное предложение для любых измерений сложности big-O: «Существует n0, так что для всех n> n0 …». Другими словами, может быть, вы еще не достигли n0? Недавно я видел видео (надеюсь, кто-то еще запомнит его), где некоторые люди пытались определить этот предел для некоторых алгоритмов, и их результаты заключались в том, что эти ограничения удивительно высокий.
Заметка: поскольку OP использует Windows, мой ответ ниже (который неверно предполагал Linux) может не подходить. Я оставил это ради тех, кто может найти эту информацию полезной.
clock()
неверный интерфейс для измерения времени в Linux: он измеряет время процессора, используемое программой (см. http://linux.die.net/man/3/clock), который в случае нескольких потоков является суммой процессорного времени для всех потоков. Вам нужно измерить прошедшее или настенное время. Смотрите больше деталей в этом вопросе: C: использование clock () для измерения времени в многопоточных программах, который также говорит, что API можно использовать вместо clock()
,
В реализации на основе MPI, с которой вы пытаетесь сравнить, используются два разных процесса (именно так MPI обычно обеспечивает параллелизм), а время ЦП второго процесса не учитывается, поэтому время ЦП близко к времени настенных часов. Тем не менее, все еще неправильно использовать процессорное время (и так clock()
) для измерения производительности даже в последовательных программах; по одной причине, если программа ожидает, например, сетевое событие или сообщение от другого процесса MPI, оно все еще тратит время, но не время процессора.
Обновить: В реализации Microsoft библиотеки времени выполнения C, clock()
возвращает время настенных часов, так что все в порядке, чтобы использовать для ваших целей. Пока неясно, используете ли вы набор инструментов Microsoft или что-то еще, например Cygwin или MinGW.