Я новичок в многопоточности и пытаюсь изучить его с помощью простой программы, которая добавляет 1 к n и возвращает сумму. В последовательном случае main
вызывает функцию sumFrom1
дважды для n = 1e5 и 2e5; в многопоточных случаях два потока создаются с использованием pthread_create
, а две суммы вычисляются в отдельном потоке. Версия многопоточности намного медленнее, чем последовательная версия (см. Результаты ниже). Я запускаю это на платформе с 12 процессорами, и между потоками нет связи.Почему многопоточность медленнее, чем последовательное программирование в моем случае?
Многопоточный:
Thread 1 returns: 0
Thread 2 returns: 0
sum of 1..10000: 50005000
sum of 1..20000: 200010000
time: 156 seconds
Sequential:
sum of 1..10000: 50005000
sum of 1..20000: 200010000
time: 56 seconds
Когда я добавляю -O2 в компиляции, время многопоточной версии (9s) меньше, чем последовательной версии (11s) , но не так много, как я ожидаю. У меня всегда есть флаг -O2, но мне любопытно узнать о низкой скорости многопоточности в неоптимизированном случае. Должна ли она быть медленнее, чем последовательная версия? Если нет, что я могу сделать, чтобы сделать это быстрее?
Код:
#include <stdio.h>
#include <pthread.h>
#include <time.h>
typedef struct my_struct
{
int n;
int sum;
}my_struct_t;
void *sumFrom1(void* sit)
{
my_struct_t* local_sit = (my_struct_t*) sit;
int i;
int nsim = 500000; // Loops for consuming time
int j;
for(j = 0; j < nsim; j++)
{
local_sit->sum = 0;
for(i = 0; i <= local_sit->n; i++)
local_sit->sum += i;
}
}
int main(int argc, char *argv[])
{
pthread_t thread1;
pthread_t thread2;
my_struct_t si1;
my_struct_t si2;
int iret1;
int iret2;
time_t t1;
time_t t2;
si1.n = 10000;
si2.n = 20000;
if(argc == 2 && atoi(argv[1]) == 1) // Use "./prog 1" to test the time of multithreaded version
{
t1 = time(0);
iret1 = pthread_create(&thread1, NULL, sumFrom1, (void*)&si1);
iret2 = pthread_create(&thread2, NULL, sumFrom1, (void*)&si2);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
t2 = time(0);
printf("Thread 1 returns: %d\n",iret1);
printf("Thread 2 returns: %d\n",iret2);
printf("sum of 1..%d: %d\n", si1.n, si1.sum);
printf("sum of 1..%d: %d\n", si2.n, si2.sum);
printf("time: %d seconds", t2 - t1);
}
else // Use "./prog" to test the time of sequential version
{
t1 = time(0);
sumFrom1((void*)&si1);
sumFrom1((void*)&si2);
t2 = time(0);
printf("sum of 1..%d: %d\n", si1.n, si1.sum);
printf("sum of 1..%d: %d\n", si2.n, si2.sum);
printf("time: %d seconds", t2 - t1);
}
return 0;
}
Update1:
После небольшого Googling на "ложном обмена" (Спасибо, @Martin Джеймс!), Я думаю, что это является основной причиной. Есть (по крайней мере) два способа это исправить:
Первый способ введения буферной зоны между двумя структурами (Спасибо, @dasblinkenlight):
my_struct_t si1;
char memHolder[4096];
my_struct_t si2;
Без -O2, время потребление уменьшается с ~ 156 до ~ 38 с.
Второй способ избежать часто обновлений sit->sum
, который может быть реализован с использованием переменного Темпа в sumFrom1
(как ответил @Jens Gustedt):
for(int sum = 0, j = 0; j < nsim; j++)
{
sum = 0;
for(i = 0; i <= local_sit->n; i++)
sum += i;
}
local_sit->sum = sum;
Без -O2, то много времени уменьшается от ~ От 156s до ~ 35s или ~ 109s (у этого есть два пика! Я не знаю почему.). С -O2 длительное время остается ~ 8 с.
В таких тестах нам нужно усреднить результаты. Сколько раз вы запускали тесты с оптимизацией -O2? И если вы запускали несколько раз, то что такое avg раз? –
si1 и si2 находятся рядом друг с другом. Ложное разделение? –
@PavanManjunath Спасибо за совет. Я побежал 10 раз с -O2. время avg составляет 7.9s для многопоточной версии и 11.7 для последовательного. Флуктуация мала. – cogitovita