如何使用PAPI来衡量并行程序的整体性能表现

9
我在思考如何最好地测量并行程序的性能(以flops为单位)。我了解了papi_flops,但这似乎仅适用于串行程序。但我不知道如何测量并行程序的整体性能。
我想要衡量blas/lapack函数的性能,例如我的例子gemm。但我也想要衡量其他函数的性能,特别是那些操作数量未知的函数。(对于gemm,已知操作数(ops(gemm) = 2*n^3),因此我可以根据操作数和执行时间计算性能。)库(我使用的是Intel MKL)会自动生成线程,因此我无法单独测量每个线程的性能然后将其降低。
以下是我的示例:
#include <stdlib.h>                                                              
#include <stdio.h>                                                               
#include <string.h>                                                             
#include "mkl.h"
#include "omp.h"
#include "papi.h"       

int main(int argc, char *argv[] )                                                
{                                                                                
  int i, j, l, k, n, m, idx, iter;
  int mat, mat_min, mat_max;
  int threads;
  double *A, *B, *C;
  double alpha =1.0, beta=0.0;

  float rtime1, rtime2, ptime1, ptime2, mflops;
  long long flpops;

  #pragma omp parallel
  {
    #pragma omp master
    threads = omp_get_num_threads();
  }

  if(argc < 4){                                                                  
    printf("pass me 3 arguments!\n");                                            
    return( -1 );                                                                
  }                                                                              
  else                                                                           
  {                                                                            
    mat_min = atoi(argv[1]);
    mat_max = atoi(argv[2]);
    iter = atoi(argv[3]);                                                         
  }                    

  m = mat_max;  n = mat_max;  k = mat_max;

  printf (" Initializing data for matrix multiplication C=A*B for matrix \n"
            " A(%ix%i) and matrix B(%ix%i)\n\n", m, k, k, n);

  A = (double *) malloc( m*k * sizeof(double) );
  B = (double *) malloc( k*n * sizeof(double) );
  C = (double *) malloc( m*n * sizeof(double) );

  printf (" Intializing matrix data \n\n");
  for (i = 0; i < (m*k); i++)
    A[i] = (double)(i+1);
  for (i = 0; i < (k*n); i++)
    B[i] = (double)(-i-1);
  memset(C,0,m*n*sizeof(double));

  // actual meassurment
  for(mat=mat_min;mat<=mat_max;mat+=5)
  {
    m = mat;  n = mat; k = mat;

    for( idx=-1; idx<iter; idx++ ){
      PAPI_flops( &rtime1, &ptime1, &flpops, &mflops );
      cblas_dgemm(CblasColMajor, CblasNoTrans, CblasNoTrans, 
                    m, n, k, alpha, A, k, B, n, beta, C, n);
      PAPI_flops( &rtime2, &ptime2, &flpops, &mflops );
    }

    printf("%d threads: %d in %f sec, %f MFLOPS\n",threads,mat,rtime2-rtime1,mflops);fflush(stdout);
  }

  printf("Done\n");fflush(stdout);

  free(A);
  free(B);
  free(C);

  return 0;
}

这是一个输出结果(对于矩阵大小为200):
1 threads: 200 in 0.001459 sec, 5570.258789 MFLOPS
2 threads: 200 in 0.000785 sec, 5254.993652 MFLOPS
4 threads: 200 in 0.000423 sec, 4919.640137 MFLOPS
8 threads: 200 in 0.000264 sec, 3894.036865 MFLOPS

我们可以看到执行时间中,gemm函数是可拓展的。但我所测量的flops仅是线程0的表现。
我的问题是:如何衡量整体性能?非常感谢您的任何建议。

测量每个线程的FLOPS,然后将它们相加? - Voo
我该如何做到这一点?blas库创建了线程。因此,平行区域在函数调用dgemm内部。我无法访问单个线程。 当然,我可以重新编译blas库,然后在并行区域内测量每个线程的性能(在MKL的情况下不可能,好吧,我可以切换到OpenBlas)。但这正是我想要避免的。 - Sebastian
你能显示浮点运算次数吗?也许 mflops 是所有线程的平均值? - paul-g
1个回答

4

首先,我很好奇 - 你为什么需要FLOPS?难道你不只关心需要多少时间吗?或者是相对于其他BLAS库所需的时间?

PAPI基于线程,本身并没有太大帮助。

我会在函数调用周围进行测量,并查看随着它生成的线程数量而改变的时间。它不应该生成比物理内核更多的线程(HT在这里没用)。然后,如果矩阵足够大且机器没有负载,则时间应该简单地除以线程数。例如,4个内核上的10秒应变成2.5秒。

除此之外,有两件事情可以真正衡量它:
1. 使用你现在使用的任何东西,但在BLAS代码周围注入你的开始/结束测量代码。在Linux中,一种方法是预加载定义pthread_start的lib,并使用您自己的函数调用原始函数但进行额外的测量。另一种方法是在进程已经运行时覆盖函数指针(=跳板)。在Linux中,它在GOT / PLT中,在Windows中它更复杂 - 寻找一个库。
2. 使用oprofile或其他分析器报告在你关心的时间内执行的指令数。或者更好的是,报告执行的浮点指令数。这个小问题是SSE指令在一次乘法或加法中同时乘以或添加2个或更多个double,因此您必须考虑到这一点。我想你可以假设它们始终使用最大可能的操作数。


首先:感谢您的回答! 我为什么想要测量性能和执行时间?实际上,我对分析LAPACK的密集特征求解器很感兴趣。密集特征求解器调用三个函数:1)约化为三对角形式,2)三对角特征求解器,3)反变换。为了确定密集特征求解器的瓶颈,有必要测量时间和性能。如果我只有执行时间,那么例如我可以看到我在约化中花费了大部分时间。但我不知道是否有效地利用了资源。因此,我不能确定这是否是瓶颈。 - Sebastian
您针对此问题提供了两个变体,我喜欢第一个。重写pthread_create(以及pthread_join)似乎是使用PAPI的唯一方法。在运行时覆盖指针对于我的情况很有意义(我的代码中有很多正确性检查,我不想测量这部分)。 - Sebastian
我能理解这个理论,但我不确定如何实现它。我需要覆盖pthread_create的函数指针。在这个函数内部,我必须使用原始的pthread_create函数创建线程,然后开始测量。我不确定如何解决被覆盖指针和原始指针的问题。我的想法是使用宏。这是最好的方法吗?总的来说:你有没有一个例子或者推荐一些阅读材料来学习更多关于这方面的知识?谢谢! - Sebastian
我将在两周后,当我面对台式电脑时,发布一个示例。 - BitWhistler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接