关于C#:在我的情况下,为什么多线程比顺序编程要慢?

Why is multithreading slower than sequential programming in my case?

我是多线程的新手,并尝试通过一个简单的程序来学习它,该程序将n加1并返回总和。在顺序情况下,对于n = 1e5和2e5,main调用sumFrom1函数两次。在多线程情况下,使用pthread_create创建两个线程,并在单独的线程中计算两个和。多线程版本比顺序版本要慢得多(请参见下面的结果)。我在12 CPU平台上运行此程序,线程之间没有通信。

多线程:

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
sum of 1..10000: 50005000
sum of 1..20000: 200010000
time: 56 seconds

当我在编译中添加-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。

第二种方法是避免频繁更新sit->sum,这可以使用sumFrom1中的temp变量来实现(如@Jens Gustedt回答):

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

发布前,请使用-Wall编译代码。