为啥n个线程的平均速度不如C中的单个线程快?

Posted

技术标签:

【中文标题】为啥n个线程的平均速度不如C中的单个线程快?【英文标题】:Why the average speed of n threads is not as fast as one single thread in C?为什么n个线程的平均速度不如C中的单个线程快? 【发布时间】:2016-06-25 21:54:02 【问题描述】:

我编写了一个有 2 个线程做同样事情的程序,但我发现每个线程的吞吐量比我只生成一个线程时要慢。然后我写了这个简单的测试,看看是我的问题还是系统的问题。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>


/*
 * Function: run_add
 * -----------------------
 * Do addition operation for iteration ^ 3 times
 *
 * returns: void
 */
void *run_add(void *ptr) 
  clock_t t1, t2;
  t1 = clock();

  int sum = 0;
  int i = 0, j = 0, k = 0;
  int iteration = 1000;
  long total = iteration * iteration * iteration;
  for (i = 0; i < iteration; i++) 
    for (j = 0; j < iteration; j++) 
      for (k = 0; k < iteration; k++) 
        sum++;
      
    
  

  t2 = clock();
  float diff = ((float)(t2 - t1) / 1000000.0F );
  printf("thread id = %d\n", (int)(pthread_self()));
  printf("Total addtions: %ld\n", total);
  printf("Total time: %f second\n", diff);
  printf("Addition per second: %f\n", total / diff);
  printf("\n");

  return NULL;



void run_test(int num_thread) 
  pthread_t pth_arr[num_thread];
  int i = 0;
  for (i = 0; i < num_thread; i++) 
    pthread_create(&pth_arr[i], NULL, run_add, NULL);
  

  for (i = 0; i < num_thread; i++) 
    pthread_join(pth_arr[i], NULL);
  


int main() 
  int num_thread = 5;
  int i = 0;
  for (i = 1; i < num_thread; i++) 
    printf("Running SUM with %d threads. \n\n", i);
    run_test(i);
  
  return 0;

结果仍然显示n个线程的平均速度比单个线程慢。我拥有的线程越多,每个线程就越慢。

结果如下:

使用 1 个线程运行 SUM。

线程 ID = 528384, 总添加量:1000000000, 总时间:1.441257秒, 每秒加法:693838784.000000

使用 2 个线程运行 SUM。

线程 ID = 528384, 总添加量:1000000000, 总时间:2.970870秒, 每秒加法:336601728.000000

线程 ID = 1064960, 总添加量:1000000000, 总时间:2.972992秒, 每秒加法:336361504.000000

使用 3 个线程运行 SUM。

线程 ID = 1064960, 总添加量:1000000000, 总时间:4.434701秒, 每秒加法:225494352.000000

线程 ID = 1601536, 总添加量:1000000000, 总时间:4.449250秒, 每秒加法:224756976.000000

线程 ID = 528384, 总添加量:1000000000, 总时间:4.454826秒, 每秒加法:224475664.000000

使用 4 个线程运行 SUM。

线程 ID = 528384, 总添加量:1000000000, 总时间:6.261967秒, 每秒加法:159694224.000000

线程 ID = 1064960, 总添加量:1000000000, 总时间:6.293107秒, 每秒加法:158904016.000000

线程 id = 2138112, 总添加量:1000000000, 总时间:6.295047秒, 每秒加法:158855056.000000

线程 ID = 1601536, 总添加量:1000000000, 总时间:6.306261秒, 每秒加法:158572560.000000

我有一个 4 核 CPU,每次我运行 n 个线程时,我的系统监视器都会显示 n 个 CPU 内核的利用率为 100%。 n个线程(

【问题讨论】:

我很惊讶程序没有立即运行;您是否在打开优化的情况下进行编译? @Hurkyl 我编译它使用:gcc xxx.c -lpthread 你弄错了多线程。您的所有线程都在做完全相同的工作,并且它们都完成了所有的工作。这不能加快进程。您必须在所有线程之间拆分工作。 @Luke:没有优化,性能测试毫无意义。添加-O2-O3(之后您可能需要发布一个关于如何编写一个没有得到优化的繁忙工作循环的新问题)。另外,我认为gcc 也喜欢使用线程编译的另一个标志; -threads-thread-pthread 或类似的东西? 时钟测量 CPU 时间,而不是墙上时间 【参考方案1】:

clock() 测量 CPU 时间而不是“Wall”时间。 它还测量所有线程的总时间..

CPU 时间是处理器执行代码的时间,墙上时间是真实世界经过的时间(就像墙上的时钟一样)

使用 /usr/bin/time 为您的程序计时,以查看实际发生的情况。 或使用 time()、gettimeofday() 或 clock_gettime() 之类的挂钟函数

clock_gettime() 可以测量此线程、此进程或挂墙时间的 CPU 时间。 - 这可能是进行此类实验的最佳方式。

【讨论】:

+1 也许您应该丰富它以使其成为更好的答案。我很确定许多 SO 用户不知道这意味着什么。【参考方案2】:

虽然您已经知道为什么多线程性能似乎比单线程更差,但您可以采取一些措施来清理程序的逻辑并使其按照您的预期工作。

首先,如果您跟踪过去的相对挂时间和clock() 次的差异报告的时间,您会注意到报告的时间大约是一个 (n-proccessor core ) 实际挂壁时间的倍数。其他答案中对此进行了解释。

对于每个核心的相对性能时序,使用clock() 就可以了。你得到的只是一个近似的挂壁时间,但是为了查看每秒的相对增加,这提供了一个清晰的每个内核的性能。

虽然您正确使用了 1000000 的除数来表示 diff,但 time.h 为您提供了一个方便的 define。 POSIX 要求 CLOCKS_PER_SEC 等于 1000000 与实际分辨率无关。该常量在time.h 中提供。

接下来,您还应该注意到,直到所有线程都加入后才会报告您的每核输出,这使得run_add 中的报告总数有些毫无意义。为方便起见,您可以从各个线程中输出thread_id 等,但应在所有线程加入后在调用函数中计算回计时信息。这将显着清理您的run_add 的逻辑。此外,如果您希望能够改变迭代次数,您应该考虑将该值传递给ptr。例如:

/*
 * Function: run_add
 * -----------------------
 * Do addition operation for iteration ^ 3 times
 *
 * returns: void
 */
void *run_add (void *ptr)

    int i = 0, j = 0, k = 0, iteration = *(int *)ptr;
    unsigned long sum = 0;

    for (i = 0; i < iteration; i++)
        for (j = 0; j < iteration; j++)
            for (k = 0; k < iteration; k++)
                sum++;

    printf ("  thread id  = %lu\n", (long unsigned) (pthread_self ()));
    printf ("  iterations = %lu\n\n", sum);

    return NULL;

run_test 相对没有变化,大部分计算更改是从 run_add 移动到 main 并根据使用的内核数量进行缩放。以下是对main 的重写,允许用户指定要用作第一个参数的核心数(默认使用all-cores)和作为第二个参数的迭代次数的基数(1000 by默认):

int main (int argc, char **argv) 

    int nproc = sysconf (_SC_NPROCESSORS_ONLN), /* number of core available */
        num_thread = argc > 1 ? atoi (argv[1]) : nproc,
        iter = argc > 2 ? atoi (argv[2]) : 1000;
    unsigned long subtotal = iter * iter * iter,
        total = subtotal * num_thread;
    double diff = 0.0, t1 = 0.0, t2 = 0.0;

    if (num_thread > nproc) num_thread = nproc;
    printf ("\nrunning sum with %d threads.\n\n", num_thread);

    t1 = clock ();
    run_test (num_thread, &iter);
    t2 = clock ();
    diff = (double)((t2 - t1) / CLOCKS_PER_SEC / num_thread);

    printf ("----------------\nTotal time: %lf second\n", diff);
    printf ("Total addtions: %lu\n", total);
    printf ("Additions per-second: %lf\n\n", total / diff);

    return 0;

将所有部分放在一起,您可以编写一个工作示例,如下所示。确保禁用优化以防止编译器针对 sum 等优化循环...

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>

/*
 * Function: run_add
 * -----------------------
 * Do addition operation for iteration ^ 3 times
 *
 * returns: void
 */
void *run_add (void *ptr)

    int i = 0, j = 0, k = 0, iteration = *(int *)ptr;
    unsigned long sum = 0;

    for (i = 0; i < iteration; i++)
        for (j = 0; j < iteration; j++)
            for (k = 0; k < iteration; k++)
                sum++;

    printf ("  thread id  = %lu\n", (long unsigned) (pthread_self ()));
    printf ("  iterations = %lu\n\n", sum);

    return NULL;


void run_test (int num_thread, int *it)

    pthread_t pth_arr[num_thread];
    int i = 0;

    for (i = 0; i < num_thread; i++)
        pthread_create (&pth_arr[i], NULL, run_add, it);

    for (i = 0; i < num_thread; i++)
        pthread_join (pth_arr[i], NULL);


int main (int argc, char **argv) 

    int nproc = sysconf (_SC_NPROCESSORS_ONLN),
        num_thread = argc > 1 ? atoi (argv[1]) : nproc,
        iter = argc > 2 ? atoi (argv[2]) : 1000;
    unsigned long subtotal = iter * iter * iter,
        total = subtotal * num_thread;
    double diff = 0.0, t1 = 0.0, t2 = 0.0;

    if (num_thread > nproc) num_thread = nproc;
    printf ("\nrunning sum with %d threads.\n\n", num_thread);

    t1 = clock ();
    run_test (num_thread, &iter);
    t2 = clock ();
    diff = (double)((t2 - t1) / CLOCKS_PER_SEC / num_thread);

    printf ("----------------\nTotal time: %lf second\n", diff);
    printf ("Total addtions: %lu\n", total);
    printf ("Additions per-second: %lf\n\n", total / diff);

    return 0;

使用/输出示例

现在,您可以根据使用的核心数量来衡量每秒执行的相对添加次数 - 并让它返回一个 Total time,这与 wall-time 大致相同。例如,使用单核测量每秒的添加量会导致:

$ ./bin/pthread_one_per_core 1

running sum with 1 threads.

  thread id  = 140380000397056
  iterations = 1000000000

----------------
Total time: 2.149662 second
Total addtions: 1000000000
Additions per-second: 465189411.172547

近似值465M additions per-sec。使用两个核心应该使这个速度翻倍:

$ ./bin/pthread_one_per_core 2

running sum with 2 threads.

  thread id  = 140437156796160
  iterations = 1000000000

  thread id  = 140437165188864
  iterations = 1000000000

----------------
Total time: 2.152436 second
Total addtions: 2000000000
Additions per-second: 929179560.000957

929M/s 每秒添加量的两倍。使用 4 核:

$ ./bin/pthread_one_per_core 4

running sum with 4 threads.

  thread id  = 139867841853184
  iterations = 1000000000

  thread id  = 139867858638592
  iterations = 1000000000

  thread id  = 139867867031296
  iterations = 1000000000

  thread id  = 139867850245888
  iterations = 1000000000

----------------
Total time: 2.202021 second
Total addtions: 4000000000
Additions per-second: 1816513309.422720

再次翻倍到1.81G/s,使用 8 核得到预期结果:

$ ./bin/pthread_one_per_core

running sum with 8 threads.

  thread id  = 140617712838400
  iterations = 1000000000

  thread id  = 140617654089472
  iterations = 1000000000

  thread id  = 140617687660288
  iterations = 1000000000

  thread id  = 140617704445696
  iterations = 1000000000

  thread id  = 140617662482176
  iterations = 1000000000

  thread id  = 140617696052992
  iterations = 1000000000

  thread id  = 140617670874880
  iterations = 1000000000

  thread id  = 140617679267584
  iterations = 1000000000

----------------
Total time: 2.250243 second
Total addtions: 8000000000
Additions per-second: 3555171004.558562

3.55G/s。查看两个答案(当前),如果您有任何问题,请告诉我们。

注意:可以应用许多额外的清理和验证,但就您的示例而言,将类型更新为有理无符号可防止带有 thread_id 和附加数字的奇怪结果.

【讨论】:

以上是关于为啥n个线程的平均速度不如C中的单个线程快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 asyncio 单线程 速度还能那么快

为啥 C# 中的多线程不能达到 100% CPU?

为啥在线程中使用 system() 时,多线程 C 程序在 Mac OS X 上被强制使用单个 CPU?

单个程序中的 10 个线程或 1 个线程程序运行 10 次(C++)?

监视同步线程的C中的源代码[关闭]

C/C++ - sem_t 类型的单个信号量按顺序打印数字