带线程的矩阵乘法:为什么它不会更快?

时间:2010-06-06 22:29:35

标签: c multithreading

所以我一直在玩pthreads,特别是试图计算两个矩阵的乘积。我的代码非常混乱,因为它本身应该是一个快速的小有趣的项目,但我使用的线程理论非常类似于:

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

#define M 3
#define K 2
#define N 3
#define NUM_THREADS 10

int A [M][K] = { {1,4}, {2,5}, {3,6} };
int B [K][N] = { {8,7,6}, {5,4,3} };
int C [M][N];

struct v {
   int i; /* row */
   int j; /* column */
};

void *runner(void *param); /* the thread */

int main(int argc, char *argv[]) {

   int i,j, count = 0;
   for(i = 0; i < M; i++) {
      for(j = 0; j < N; j++) {
         //Assign a row and column for each thread
         struct v *data = (struct v *) malloc(sizeof(struct v));
         data->i = i;
         data->j = j;
         /* Now create the thread passing it data as a parameter */
         pthread_t tid;       //Thread ID
         pthread_attr_t attr; //Set of thread attributes
         //Get the default attributes
         pthread_attr_init(&attr);
         //Create the thread
         pthread_create(&tid,&attr,runner,data);
         //Make sure the parent waits for all thread to complete
         pthread_join(tid, NULL);
         count++;
      }
   }

   //Print out the resulting matrix
   for(i = 0; i < M; i++) {
      for(j = 0; j < N; j++) {
         printf("%d ", C[i][j]);
     }
      printf("\n");
   }
}

//The thread will begin control in this function
void *runner(void *param) {
   struct v *data = param; // the structure that holds our data
   int n, sum = 0; //the counter and sum

   //Row multiplied by column
   for(n = 0; n< K; n++){
      sum += A[data->i][n] * B[n][data->j];
   }
   //assign the sum to its coordinate
   C[data->i][data->j] = sum;

   //Exit the thread
   pthread_exit(0);
}

来源:http://macboypro.com/blog/2009/06/29/matrix-multiplication-in-c-using-pthreads-on-linux/

对于非线程版本,我使用了相同的设置(3个2-d矩阵,动态分配的结构来保存r / c),并添加了一个计时器。第一次试验表明非线程版本更快。我的第一个想法是尺寸太小而不能注意到差异,并且创建线程需要更长的时间。所以我将尺寸增加到大约50x50,随机填充并运行它,我仍然没有看到使用线程版本进行任何性能升级。

我在这里缺少什么?

5 个答案:

答案 0 :(得分:11)

除非您使用非常大型矩阵(数千行/列),否则您不太可能从这种方法中看到太多改进。在现代CPU / OS上设置线程实际上相对于CPU时间来说相当昂贵,比一些乘法操作要花费更多的时间。

此外,通常不值得为每个CPU核心设置多个线程。如果您只有两个内核并且设置了2500个线程(对于50x50矩阵),则操作系统将花费所有时间来管理和切换这2500个线程,而不是进行计算。

如果您事先设置了两个线程(仍然假设是一个双核CPU),请保持这些线程始终可用,等待工作,并为它们提供您需要计算的2500点产品同步工作队列,然后您可能开始看到改进。但是,它仍然不会比仅使用一个核心好50%以上。

答案 1 :(得分:7)

我不完全确定我理解源代码,但这就是它的样子:你有一个运行M * N次的循环。每次循环时,都会创建一个在结果矩阵中填入一个数字的线程。但是在你启动线程之后,你就等着它完成了。我认为你实际上并没有运行多个线程。

即使您运行的是多个线程,该线程也会做很少的工作。即使K很大(你提到50),50次乘法与首先启动线程的成本相比并不多。该程序应该创建更少的线程 - 当然不超过处理器的数量 - 并为每个线程分配更多的工作。

答案 2 :(得分:1)

你不允许太多的并行执行:你在创建它后立即等待线程,所以你的程序几乎没有办法使用额外的CPU(即它永远不能使用第三个CPU /核心)。尝试允许运行更多线程(可能达到您拥有的核心数)。

答案 3 :(得分:1)

如果你有一个带有两个内核的处理器,那么你应该将工作分成两半并将每个线程分成两半。如果您有3个,4个,5个核心,则采用相同的原则。最佳性能设计将始终将线程数与可用内核数量相匹配(可用我指的是其他进程尚未大量使用的内核)。

您必须考虑的另一件事是每个线程必须使其数据连续且独立于其他线程的数据。否则,memcache未命中将会大大减慢处理速度。

为了更好地理解这些问题,我建议使用并行编程模式这本书 http://astore.amazon.com/amazon-books-20/detail/0321228111

虽然它的代码示例更多地针对OpenMP&amp; MPI,你正在使用PThreads,本书的前半部分仍然非常丰富的基本概念和多线程环境的内部工作,非常有用,可以避免遇到的大多数性能瓶颈。

答案 4 :(得分:0)

如果代码正确并行化(我不会检查它),只有当代码在硬件中并行化时,性能才会提升,即线程实际上是并行的(多核,多cpus,......其他技术......)而且显然不是(“多任务”方式)并行。只是一个想法,我不确定是这种情况。