4

我正在编写一个 C 程序,用于pthreads执行一些矩阵乘法 (C=A*B),然后计算结果的最大行和范数,这是唯一需要同步的部分,因为矩阵乘法本身是独立分布的。每个线程获取自己的矩阵 A 行的切片,然后将其行乘以 B 并将它们存储到 C 的相应行中。因此,所有线程在读取相同的内存位置时写入不同的内存位置,而不会被修改。

现在的问题是,当我在我的 MacBook(2012 年中,双核)上用两个线程编译程序时,无论我制作多大的矩阵,线程版本几乎与没有线程的版本一样快。可以预期,对于小矩阵大小,线程生成的开销等会消除加速,但这不是这里的问题。奇怪的是,如果我在 Red Hat Linux 服务器上运行相同的程序,加速非常明显,而在我的计算机上,串行版本总是快一点。服务器有 16 个内核,但我只使用两个线程(否则将毫无意义)。

有没有人有任何建议为什么同一个程序在服务器上显示加速但在我的 MacBook 上没有?这是一些代码,因此您可以看到我在做什么。

编辑:我尝试在 Mac 上使用 clang 而不是 gcc 进行编译,并且 - 神奇地 - 正如你所期望的那样,加速就在那里。欢迎任何解释,为什么 gcc 不能在不同的 CPU 上分配线程,但 clang 可以。有人会认为我不是第一个经历这种情况的人。

#include <stdlib.h>
#include <sys/time.h>
#include <stdio.h>
#include "assignment3.h"

int main(int argc, const char *argv[])
{
   int n, p = 1;
   printf("Please enter the number of processors.\n > ");
   scanf("%d", &p);
   do {
      printf("Please enter the matrix factor "
            "(matrix size is a multiple of processor number).\n > ");
         scanf("%d", &n);
   } while(n % p);
   /* printf("n and p have values %d and %d\n", n, p); */

   // make matrices
   double **A, **B, **C;
   A = malloc(n*sizeof(double *));
   B = malloc(n*sizeof(double *));
   C = malloc(n*sizeof(double *));
   if (A == NULL || B == NULL || C == NULL) {
      return 1;
   }
   A[0] = malloc(n*n*sizeof(double));
   B[0] = malloc(n*n*sizeof(double));
   C[0] = calloc(n*n, sizeof(double));
   int i;
   for (i = 1; i < n; i++) {
      A[i] = A[0] + i*n;
      B[i] = B[0] + i*n;
      C[i] = C[0] + i*n;
   }

   // Fill with random values between 0 and 1
   fillMatrix(n, A);
   fillMatrix(n, B);

   pthread_t *workers = malloc(p * sizeof(pthread_t));
   thread_data_t *data = malloc(p * sizeof(thread_data_t));
   pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
   double global_norm;
   void **status = malloc(sizeof(void *)); // leaving out malloc is fine on osx, not on linux

   for (i = 0; i < p; i++) {
      data[i].n = n;
      data[i].p = p;
      data[i].my_row = i * n/p;
      data[i].A = A;
      data[i].B = B;
      data[i].C = C;
      data[i].mutex = &mutex;
      data[i].global_norm = &global_norm;
   }


   struct timeval tv1, tv2;
   struct timezone tz;

   gettimeofday(&tv1, &tz);
   for (i = 0; i < p; i++) // multiply uses ATLAS to multiply this thread's portion of the matrices
      pthread_create(&workers[i], NULL, multiply, &data[i]);

   for (i = 0; i < p; i++) pthread_join(workers[i], status);
   gettimeofday(&tv2, &tz);
   double elapsed = (double) (tv2.tv_sec - tv1.tv_sec) + (double)
      (tv2.tv_usec - tv1.tv_usec) * 1.e-6;

   // do non-parallel computation
   struct timeval tv3, tv4;
   struct timezone tz2;
   gettimeofday(&tv3, &tz2);
   double global_norm_serial = multiply_serial(A, B, C, n); // plain serial multiply with ATLAS
   gettimeofday(&tv4, &tz2);
   double elapsed_serial = (double) (tv4.tv_sec - tv3.tv_sec) + (double)
      (tv4.tv_usec - tv3.tv_usec) * 1.e-6;

   printf("Time elapsed for parallel execution: %lf seconds.\n", elapsed);
   printf("Time elapsed for serial execution: %lf seconds.\n", elapsed_serial);



   /* puts("Matrix A:"); */
   /* printMatrix(n, A); */
   /* puts("Matrix B:"); */
   /* printMatrix(n, B); */
   /* puts("Matrix C:"); */
   /* printMatrix(n, C); */
   printf("The maximum row sum norm is %lf.\n", global_norm);
   printf("The maximum (serial) row sum norm is %lf.\n", global_norm_serial);

   free(A[0]);
   free(B[0]);
   free(C[0]);
   free(A);
   free(B);
   free(C);
   free(workers);
   free(data);
   pthread_mutex_destroy(&mutex);
   return 0;
}

EDIT2: 我不知道这有什么帮助,但这就是multiply程序的作用:

void *multiply(void *arg){
   thread_data_t *data = arg;
   int n = data->n, 
       p = data->p, 
       my_row = (*data).my_row;
   cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, n/p, n, n, 1.0,
         (data->A)[my_row], n, (data->B)[0], n, 0.0, (data->C)[my_row], n);

   pthread_mutex_lock(data->mutex);
   double max = findMax(data->C + my_row, n, n/p, data->my_row);
   if (*(data->global_norm) < max) 
      *(data->global_norm) = max;
   pthread_mutex_unlock(data->mutex);

   pthread_exit(NULL);
   return NULL;
}

该类型thread_data_t具有以下成员:

typedef struct {
   int n, p, my_row;
   double **A;
   double **B;
   double **C;
   double *global_norm;
   pthread_mutex_t *mutex;
} thread_data_t;
4

2 回答 2

2

好的,所以对于它的价值,我的导师和我似乎已经发现了问题所在。我没有提供任何关于我如何编译和链接我的程序的信息,所以没有人可能真的猜到了。至少不是没有天才的罢工。

没有显示任何加速的情况的编译是这样完成的:

gcc-mp-4.7 $(CFILES) $(MANUALPROG) -lcblas -latlas -Wall -o assignment3

CFILES我的源文件在哪里,MANUALPROG只是两个不同版本之间的切换。

好吧,我使用的是 MacPorts 安装的 gcc 4.7。我没有具体包括我的位置libblas.a——因为没有错误——假设它在这个编译器的标准搜索路径中。我认为这个搜索路径包括 MacPorts 目录/opt/local/lib,其中libblas.alibatlas.a驻留。所以我假设 gcc 会链接到这些库。嗯,不。

如果不是上面的,我使用

gcc-mp-4.7 -L/opt/local/lib $(CFILES) $(MANUALPROG) -lcblas -latlas -Wall -o assignment3

然后加速是可见的。因此,该问题与 clang 或特定编译器无关,Apple 的 gcc 也是如此。这只是使用了哪些库的问题。

因此,由于 OS X 没有该ldd工具,我otool -L在我的可执行文件上使用并发现在第一种情况下,输出是这样的

$> otool -L assignment3
assignment3:
    /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
    /opt/local/lib/libgcc/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)

相反,对于第二种情况,就是这样

$> otool -L assignment3
assignment3:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
    /opt/local/lib/libgcc/libgcc_s.1.dylib (compatibility version 1.0.0, current version 1.0.0)

事实证明,如果我没有明确指定库所在的路径,编译器会动态链接到 Apple 提供的 BLAS 版本。通过明确包含 MacPorts 版本,可以防止这种情况发生。

现在,回到为什么一个版本更快:我的导师说 BLAS/ATLAS 有不同的实现,它们本身可以是多线程的,也可以不是。现在看起来Apple 的版本是多线程的,而 MacPorts 的不是。为什么?因为假设我正在调用的 BLAS 例程本身是线程化的,那么我自己创建线程然后在每个线程中调用例程或者我是否在一次调用例程中放入整个矩阵都没有关系——它将是无论如何分成线程。当然,我创建自己的线程会消耗更多时间,这解释了“并行”执行时间总是稍长一点的事实。

另一方面,如果 BLAS 例程没有线程化,那么我们在创建自己的线程时应该会看到一些差异——这就是发生的情况。静态链接到 MacPorts 库意味着程序中有串行 BLAS 调用。因此,当我将它们划分为线程时,我们应该会看到加速——而且确实如此。事实上,动态链接到 Apple 框架时的执行时间与静态链接到 MacPorts 库并使用我自己的线程的版本的执行时间大致相同(略少)。

我无法确定一个或另一个 BLAS 实现是否是线程化的,但解释符合数据。

于 2013-10-30T18:34:07.733 回答
-2

我认为最可能的原因是您不能确定这 2 个线程将在不同的处理器上运行。您需要研究操作系统如何决定分配处理器资源。

于 2013-10-26T15:56:44.510 回答