多线程如何提供比核心数更大的speeedup因子?

时间:2017-07-06 20:42:09

标签: c multithreading

我正在使用gth的pthreads。简单的代码示例采用线程数" N"作为用户提供的输入。它将一个长数组分成N个大致相等大小的子块。每个子块都由各个线程写入。

此示例的虚拟处理实际上涉及为每个数组索引休眠一段固定的时间,然后将数字写入该数组位置。

这里是代码:

/******************************************************************************
* FILE: threaded_subblocks_processing
* DESCRIPTION:
* We have a bunch of parallel processing to do and store the results in a
* large array. Let's try to use threads to speed it up.
******************************************************************************/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#define BIG_ARR_LEN 10000

typedef struct thread_data{
  int start_idx;
  int end_idx;
  int id;
} thread_data_t;

int big_result_array[BIG_ARR_LEN] = {0};

void* process_sub_block(void *td)
{
   struct thread_data *current_thread_data = (struct thread_data*)td;
   printf("[%d] Hello World! It's me, thread #%d!\n", current_thread_data->id, current_thread_data->id);
   printf("[%d] I'm supposed to work on indexes %d through %d.\n", current_thread_data->id, 
       current_thread_data->start_idx, 
       current_thread_data->end_idx-1);

   for(int i=current_thread_data->start_idx; i<current_thread_data->end_idx; i++)
   {
       int retval = usleep(1000.0*1000.0*10.0/BIG_ARR_LEN);
       if(retval)
       {
         printf("sleep failed");
       }

       big_result_array[i] = i;
   }

   printf("[%d] Thread #%d done, over and out!\n", current_thread_data->id, current_thread_data->id);
   pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
   if (argc!=2)
   {
     printf("usage: ./a.out number_of_threads\n");
     return(1);
   }

   int NUM_THREADS = atoi(argv[1]);

   if (NUM_THREADS<1)
   {
     printf("usage: ./a.out number_of_threads (where number_of_threads is at least 1)\n");
     return(1);
   }

   pthread_t *threads = malloc(sizeof(pthread_t)*NUM_THREADS);
   thread_data_t *thread_data_array = malloc(sizeof(thread_data_t)*NUM_THREADS);

   int block_size = BIG_ARR_LEN/NUM_THREADS;
   for(int i=0; i<NUM_THREADS-1; i++)
   {
     thread_data_array[i].start_idx = i*block_size;
     thread_data_array[i].end_idx = (i+1)*block_size;
     thread_data_array[i].id = i;
   }
   thread_data_array[NUM_THREADS-1].start_idx = (NUM_THREADS-1)*block_size;
   thread_data_array[NUM_THREADS-1].end_idx = BIG_ARR_LEN;
   thread_data_array[NUM_THREADS-1].id = NUM_THREADS;

   int ret_code;
   long t;
   for(t=0;t<NUM_THREADS;t++){
     printf("[main] Creating thread %ld\n", t);
     ret_code = pthread_create(&threads[t], NULL, process_sub_block, (void *)&thread_data_array[t]);
     if (ret_code){
       printf("[main] ERROR; return code from pthread_create() is %d\n", ret_code);
       exit(-1);
       }
     }

   printf("[main] Joining threads to wait for them.\n");
   void* status;
   for(int i=0; i<NUM_THREADS; i++)
   {
     pthread_join(threads[i], &status);
   }

   pthread_exit(NULL);
}

我用

编译它
gcc -pthread threaded_subblock_processing.c

然后我从命令行调用它:

$ time ./a.out 4

当我增加线程数时,我看到了加速。使用1个线程,该过程只需要10秒多一点。这是有道理的,因为我每个数组元素睡眠1000个usec,并且有10,000个数组元素。接下来,当我转到2个线程时,它会下降到5秒多一点,依此类推。

我不明白的是,即使我的线程数超过计算机上的内核数量,我也能获得加速!我有4个核心,因此我预计不会为&gt; 4个线程加速。但是,令人惊讶的是,当我跑步时

$ time ./a.out 100

我获得了100倍的加速,处理在~0.1秒内完成!这怎么可能?

1 个答案:

答案 0 :(得分:2)

一些一般背景

程序的进展可以通过很多方面减慢,但是,一般来说,你可以将点(也称为热点)减缓为两类:

  • CPU绑定:在这种情况下,处理器正在进行一些重数字运算(如三角函数)。如果所有CPU核心都参与了这些任务,则其他进程必须等待。

  • 内存绑定:在这种情况下,处理器正在等待从硬盘或RAM中检索信息。由于这些通常比处理器慢几个数量级,因此从CPU的角度来看,这需要永远

但您也可以想象一个进程必须等待的其他情况,例如网络响应。

在许多这些受内存/网络限制的情况下,可以暂停一个线程&#34;在此期间,内存会爬向CPU并执行其他有用的工作。如果做得好,那么多线程程序可以很好地执行其单线程等效项。 Node.js利用这种异步编程技术来实现良好的性能。

以下是对各种延迟的简单描述:Latency numbers every programmer should know

您的问题

现在,回到你的问题:你有多个线程,但它们既不执行CPU密集型工作也不执行内存密集型工作:那里没有太多时间需要花时间。实际上,sleep函数实际上是告诉操作系统正在进行 no 工作。在这种情况下,操作系统可以在线程休眠时在其他线程中工作。因此,表面性能自然会显着提高。

请注意,对于低延迟应用程序(如MPI),busy waiting有时是used而不是睡眠功能。在这种情况下,程序进入紧密循环并反复检查条件。在外部,效果看起来类似,但睡眠不使用CPU,而忙等待使用~100%的CPU。