Распараллеливание скалярного произведения, чем больше ядер, тем медленнее?

Я пытаюсь распараллелить операцию скалярного произведения и измеряю время выполнения операции на различном количестве ядер с помощью OpenMP. Я получаю результат, что если N = 1e9, то для 1 ядра процессорное время составляет 5,6 секунды, для 8 ядер 6,0 секунды и для 16 ядер 10,8 секунды. Почему время вычислений увеличивается, когда я использую больше ядер?

Вот мой код:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <omp.h>

#define DATA_TYPE float

const int N = 1e9;

int main ()
{
  int i, nthreads, tid;
  DATA_TYPE x_par, *y, *z, cput_par;
  clock_t start, end;

  y = (DATA_TYPE*)malloc(sizeof(DATA_TYPE)*N);
  z = (DATA_TYPE*)malloc(sizeof(DATA_TYPE)*N);

  for (i=0; i<N; i++) {
      y[i] = i * 1.0;
      z[i] = i * 2.0;
  }

  x_par = 0;

  //nthreads = omp_get_max_threads();
  nthreads = 1;
  printf("n threads = %d\n", nthreads);
  start=clock();
  omp_set_num_threads(nthreads);
  #pragma omp parallel for reduction(+:x_par)
      for (i=0; i<N; i++)
      {
          x_par += y[i] * z[i];
      }
  end=clock();
  cput_par = ((double)(end-start)/(double)(CLOCKS_PER_SEC));
  printf("Parallel time use: %f\n", cput_par);
  printf("x_par = %f\n", x_par);

  return 0;
}

person neckutrek    schedule 25.05.2015    source источник
comment
У вас 16 физических ядер или вы используете гиперпоточность? Гиперпоточность — это плохо для производительности   -  person    schedule 26.05.2015
comment
omp_get_max_threads() возвращает 16 на моей машине. Мое оборудование — Intel Xeon E5520 с 4 ядрами и 8 потоками. Однако время вычислений при использовании 8 потоков вместо 1 немного увеличивается. Почему?   -  person neckutrek    schedule 26.05.2015
comment
Попробуйте изменить nthreads на 4 и посмотрите, что произойдет. Если ваша архитектура поддерживает только 8 потоков, я не знаю, почему omp_get_max_threads() вернет 16. Что касается того, почему он замедляется для 8 против 1, когда вы запускаете его на 8 потоках, вы используете гиперпоточность. То есть у вас есть только 4 физических ядра, каждое из которых имеет по два потока. Каждый раз, когда вы используете оба потока на ядре для одного и того же процесса, это называется гиперпоточностью.   -  person    schedule 26.05.2015
comment
Почему бы вам не попробовать использовать omp_get_wtime() вместо clock()? На какой ОС вы проводили эти тесты?   -  person Z boson    schedule 26.05.2015
comment
@R_Kapp, я забыл упомянуть, что мое оборудование включает в себя два таких процессора, всего 8 ядер. @Z boson, использование omp_get_wtime() дает мне время 1,07 секунды вместо 6,65 секунды, если я использую часы() (для N = 1e9). Кто из них прав? Я использую ArchLinux. Я измеряю вне распараллеленной части программы.   -  person neckutrek    schedule 26.05.2015
comment
Вы также измеряете время создания потока (поскольку вы синхронизируете первую параллельную область в коде). Это вряд ли будет представлять ваш реальный тестовый пример. (Помимо мета-вопроса «Почему вы не используете оптимизированную математическую библиотеку и, в идеале, подпрограмму BLAS более высокого уровня? Есть люди, которые тратят все свое время на настройку этих функций!)   -  person Jim Cownie    schedule 26.05.2015
comment
@JimCownie, как я могу исключить создание темы из моего измерения времени стены? Я не хочу использовать для этого подпрограмму BLAS, так как моя цель — измерить ускорение по сравнению со строгим последовательным случаем, т. е. та же задача, только распараллеленная.   -  person neckutrek    schedule 26.05.2015
comment
@MarcusAJohansson, omp_get_wtime() возвращает время стены. Это то, что ты хочешь. Clock не возвращает время стены с несколькими потоками, за исключением библиотек MSFT C в Windows. Ваша проблема решена сейчас?   -  person Z boson    schedule 26.05.2015
comment
@Zboson, я подумал, что было бы лучше использовать часы (), а затем разделить это на количество используемых потоков, иначе другие программы, которые могут выполняться параллельно на машине, могут увеличить время стены, не так ли?   -  person neckutrek    schedule 26.05.2015
comment
Я не знаю. Почему бы вам не задать вопрос об этом? Я всегда использую omp_get_wtime(). В системе, где другие пользователи, вероятно, будут выполнять задания, которые могут иметь значение. В соответствии с этой link объединенное время процессора всех потоков как возвращаемое clock(), обычно больше, чем время настенных часов, измеренное omp_get_wtime(), за исключением случаев, когда ваше приложение в основном спит или ждет. Кажется, это не согласуется с вашим результатом, поэтому это был бы интересный вопрос.   -  person Z boson    schedule 26.05.2015
comment
@MarcusAJohansson, как я могу исключить создание темы из моего измерения времени стены? Выполните один пустой параллельный регион (чтобы заставить среду выполнения создать пул потоков) перед запуском таймера.   -  person Jim Cownie    schedule 27.05.2015


Ответы (1)


Ошибка заключалась в том, что было рассчитано общее время ЦП всех используемых ядер/потоков. Чтобы получить среднее время процессора для каждого потока, это значение необходимо разделить на количество потоков. Другим способом ее решения может быть измерение времени стены (т. е. разницы фактического времени дня до и после операции). Если используется время стены, то операционная система может запускать другую программу между ними, и это также включается в время стены. Чтобы проиллюстрировать это, вместе со сравнением для случая строгой последовательности я публикую этот код:

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h> //gettimeofday()
#include <time.h>
#include <omp.h>

#define DATA_TYPE float

const int N = 1e9;

int main ()
{
    int i, nthreads, tid;
    DATA_TYPE x_seq, x_par, *y, *z;
    struct timeval time;
    double tstart_cpu, tend_cpu, tstart_wall, tend_wall;
    double walltime_seq, walltime_par, cputime_seq, cputime_par;

    nthreads = 8;
    printf("- - -DOT PROCUCT: OPENMP - - -\n");
    printf("Vector size           : %d\n", N);
    printf("Number of threads used: %d\n", nthreads);


    // INITIALIZATION

    y = (DATA_TYPE*)malloc(sizeof(DATA_TYPE)*N);
    z = (DATA_TYPE*)malloc(sizeof(DATA_TYPE)*N);

    for (i=0; i<N; i++) {
        y[i] = i * 1.0;
        z[i] = i * 2.0;
    }

    x_seq = 0;
    x_par = 0;


    // SEQUENTIAL CASE

    gettimeofday(&time, NULL);
    tstart_cpu = (double)clock()/CLOCKS_PER_SEC;
    tstart_wall = (double)time.tv_sec + (double)time.tv_usec * .000001;

    for (i=0; i<N; i++) x_seq += y[i] * z[i];

    tend_cpu = (double)clock()/CLOCKS_PER_SEC;
    gettimeofday(&time, NULL);
    tend_wall = (double)time.tv_sec + (double)time.tv_usec * .000001;

    cputime_seq = tend_cpu-tstart_cpu;
    walltime_seq = tend_wall - tstart_wall;
    printf("Sequential CPU time: %f\n", cputime_seq);
    printf("Sequential Walltime: %f\n", walltime_seq);
    printf("Sequential result  : %f\n", x_seq);


    // PARALLEL CASE

    gettimeofday(&time, NULL);
    tstart_cpu = (double)clock()/CLOCKS_PER_SEC;
    tstart_wall = (double)time.tv_sec + (double)time.tv_usec * .000001;

    omp_set_num_threads(nthreads);
    #pragma omp parallel for reduction(+:x_par)
    for (i=0; i<N; i++)
    {
        x_par += y[i] * z[i];
    }

    tend_cpu = (double)clock()/CLOCKS_PER_SEC;
    gettimeofday(&time, NULL);
    tend_wall = (double)time.tv_sec + (double)time.tv_usec * .000001;

    cputime_par = tend_cpu - tstart_cpu;
    walltime_par = tend_wall - tstart_wall;
    cputime_par /= nthreads; // take the average cpu time per thread
    printf("Parallel CPU time  : %f\n", cputime_par);
    printf("Parallel Walltime  : %f\n", walltime_par);
    printf("Parallel result    : %f\n", x_par);


    // SPEEDUP

    printf("Speedup (cputime)  : %f\n", cputime_seq/cputime_par);
    printf("Speedup (walltime) : %f\n", walltime_seq/walltime_par);

    return 0;
}

И типичный запуск этого выводит:

- - -DOT PROCUCT: OPENMP - - -
Vector size           : 1000000000
Number of threads used: 8
Sequential CPU time: 4.871956
Sequential Walltime: 4.878946
Sequential result  : 38685626227668133590597632.000000
Parallel CPU time  : 0.751475
Parallel Walltime  : 0.757933
Parallel result    : 133586303067416523805032448.000000
Speedup (cputime)  : 6.483191
Speedup (walltime) : 6.437172

Как видите, результирующее скалярное произведение неверно, но это отвечает на первоначальный вопрос.

person neckutrek    schedule 26.05.2015