Как измерить общую производительность параллельных программ (с помощью papi)

Я спросил себя, как лучше всего измерить производительность (в флопах) параллельной программы. Я читал про 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 масштабируется. Но флопы, которые я измеряю, — это только производительность потока 0.

Мой вопрос: как я могу измерить общую производительность? Я благодарен за любой вклад.


person Sebastian    schedule 29.07.2015    source источник
comment
Хм.. Измерить флопы для каждого потока, а затем сложить их вместе?   -  person Voo    schedule 29.07.2015
comment
Как я могу это сделать? Библиотека blas создает потоки. Итак, параллельная область находится внутри вызова функции dgemm. У меня нет доступа к отдельным темам. Конечно, я мог бы перекомпилировать библиотеку blas, а затем внутри параллельной области измерить производительность для каждого потока (невозможно в случае с MKL, ладно, я мог бы переключиться на OpenBlas). Но именно этого я хочу избежать.   -  person Sebastian    schedule 29.07.2015
comment
Не могли бы вы показать количество флопов? Может быть, mflops - это среднее значение по всем потокам?   -  person paul-g    schedule 30.07.2015


Ответы (1)


Во-первых, мне просто интересно - зачем вам FLOPS? тебя не волнует, сколько времени уходит? или, может быть, время по сравнению с другими библиотеками BLAS?

PAPI основан на потоках, сам по себе здесь мало что может помочь.

Что бы я сделал, так это измерил вызов функции и посмотрел, как время меняется в зависимости от количества потоков, которые он порождает. Он не должен порождать больше потоков, чем физических ядер (HT здесь не годится). Затем, если матрица достаточно велика, а машина не загружена, время должно просто делиться на количество потоков. Например, 10 секунд на 4 ядрах должны стать 2,5 секунды.

Помимо этого, есть 2 вещи, которые вы можете сделать, чтобы действительно измерить это:
1. Используйте то, что вы используете сейчас, но добавьте свой код начала/конца измерения вокруг кода BLAS. Один из способов сделать это (в Linux) — предварительно загрузить библиотеку, которая определяет pthread_start, и использовать собственные функции, которые вызывают оригиналы, но выполняют некоторые дополнительные измерения. Еще один способ переопределить указатель функции, когда процесс уже запущен (=батут). В linux это в GOT/PLT, а в windows это сложнее - ищите библиотеку.
2. Используйте oprofile или какой-либо другой профилировщик, чтобы сообщить количество инструкций, выполненных за нужное вам время. Или еще лучше, чтобы сообщить о количестве выполненных инструкций с плавающей запятой. Небольшая проблема заключается в том, что инструкции SSE умножают или добавляют 2 или более двойных значений за раз, поэтому вам придется это учитывать. Я думаю, вы можете предположить, что они всегда используют максимально возможные операнды.

person BitWhistler    schedule 03.08.2015
comment
Прежде всего: Спасибо за ответ! Почему я хочу измерять производительность и время выполнения? Я действительно заинтересован в анализе плотного собственного решателя LAPACK. Плотные собственные решатели вызывают три функции: 1) приведение к трехдиагональной форме, 2) трехдиагональный собственный решатель, 3) обратное преобразование. Чтобы определить узкие места плотного алгоритма собственных вычислений, необходимо измерить время и производительность. Если у меня есть только время выполнения, то, например. Я мог видеть, что я провожу большую часть времени в сокращении. Но я не знаю, эффективно ли я использую ресурсы. Поэтому я не могу быть уверен, что это узкое место. - person Sebastian; 08.08.2015
comment
Вы предложили два варианта решения этой задачи. Мне нравится первый. Перезапись pthread_create (а также pthread_join) кажется единственной причиной для работы с PAPI. Перезапись указателя во время выполнения имеет смысл для моего случая (в моем коде много проверок правильности, я не хочу измерять и эту часть). - person Sebastian; 08.08.2015
comment
Я мог понять теорию, но я не уверен, как я мог это реализовать. Мне пришлось бы перезаписать указатель функции на pthread_create. Внутри этой функции я должен создать поток с помощью оригинальной функции pthread_create, после чего начать измерение. Я не совсем уверен, как решить проблему с перезаписанным указателем и исходным указателем. Моя идея для этого макросы. Это лучший способ? В целом: есть ли у вас пример или рекомендации по чтению, чтобы узнать больше об этом? Спасибо! - person Sebastian; 08.08.2015
comment
Я выложу пример, когда буду перед настольным компьютером - через две недели - person BitWhistler; 11.08.2015