Why is multithreading slower than sequential programming in my case?
我是多线程的新手,并尝试通过一个简单的程序来学习它,该程序将n加1并返回总和。在顺序情况下,对于n = 1e5和2e5,
多线程:
1 2 3 4 5 | Thread 1 returns: 0 Thread 2 returns: 0 sum of 1..10000: 50005000 sum of 1..20000: 200010000 time: 156 seconds |
顺序:
1 2 3 |
当我在编译中添加-O2时,多线程版本(9s)的时间比顺序版本(11s)的时间短,但并不比我期望的多。我总是可以打开-O2标志,但是我对未优化的情况下多线程的低速度感到好奇。它应该比顺序版本慢吗?如果没有,我该怎么做才能使其更快?
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | #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\ ",iret1); printf("Thread 2 returns: %d\ ",iret2); printf("sum of 1..%d: %d\ ", si1.n, si1.sum); printf("sum of 1..%d: %d\ ", 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\ ", si1.n, si1.sum); printf("sum of 1..%d: %d\ ", si2.n, si2.sum); printf("time: %d seconds", t2 - t1); } return 0; } |
UPDATE1:
在仔细研究了"虚假共享"之后(谢谢@Martin James!),我认为这是主要原因。有(至少)两种方法可以修复它:
第一种方法是在两个结构之间插入缓冲区(谢谢,@ dasblinkenlight):
1 2 3 | my_struct_t si1; char memHolder[4096]; my_struct_t si2; |
在没有-O2的情况下,耗时从?156s减少到?38s。
第二种方法是避免频繁更新
1 2 3 4 5 6 7 | 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时,耗时保持约8s。
通过将代码修改为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | typedef struct my_struct { size_t n; size_t sum; }my_struct_t; void *sumFrom1(void* sit) { my_struct_t* local_sit = sit; size_t nsim = 500000; // Loops for consuming time size_t n = local_sit->n; size_t sum = 0; for(size_t j = 0; j < nsim; j++) { for(size_t i = 0; i <= n; i++) sum += i; } local_sit->sum = sum; return 0; } |
现象消失了。您遇到的问题:
-
对于这种测试,使用
int 作为数据类型是完全错误的。你的
指出总和溢出的地方。有符号类型的溢出是未定义的行为。您很幸运,它没有吃午餐。 -
具有边界和求和变量与间接购买
额外的加载和存储,在-O0 的情况下,实际上是这样完成的:
这样,就包含了错误共享和类似的东西的所有隐含含义。
您的代码还发现了其他错误:
-
缺少
atoi 的包含 -
往返于
void* 的多余 -
将
time_t 打印为int
发布前,请使用