繁体   English   中英

矩阵乘法,KIJ顺序,并行版本比非并行版本慢

[英]Matrix multiplication, KIJ order, Parallel version slower than non-parallel

我有关于paralel编程的学校任务,我遇到了很多问题。 我的任务是创建给定矩阵乘法代码的并行版本并测试其性能(是的,它必须以KIJ顺序):

void multiply_matrices_KIJ()
{
    for (int k = 0; k < SIZE; k++)
        for (int i = 0; i < SIZE; i++)
            for (int j = 0; j < SIZE; j++)
                matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
}

这是我到目前为止提出的:

void multiply_matrices_KIJ()
{
    for (int k = 0; k < SIZE; k++)
#pragma omp parallel
    {
#pragma omp for schedule(static, 16)
        for (int i = 0; i < SIZE; i++)
            for (int j = 0; j < SIZE; j++)
                matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
    }
}

这就是我发现让我困惑的地方。 这个并行版本的代码运行速度比非并行版慢50%。 基于矩阵大小,速度的差异只有一点点的变化(测试的SIZE = 128,256,512,1024,2048和各种计划版本 - 动态,静态,到目前为止还没有它等)。

有人能帮助我理解我做错了什么吗? 是因为我使用KIJ订单而且使用openMP不会更快?

编辑:

我正在使用Visual Studio 2015社区版在Windows 7 PC上工作,在Release x86模式下进行编译(x64也没有帮助)。 我的CPU是:英特尔酷睿i5-2520M CPU @ 2,50GHZ(是的,是的,它是一台笔记本电脑,但我在我的家用I7 PC上获得相同的结果)

我正在使用全局数组:

float matrix_a[SIZE][SIZE];    
float matrix_b[SIZE][SIZE];    
float matrix_r[SIZE][SIZE];

我将随机(浮点)值赋给矩阵a和b,矩阵r用0填充。

到目前为止,我已经测试了各种矩阵大小的代码(128,256,512,1024,2048等)。 对于其中一些,它不适合缓存。 我当前的代码版本如下所示:

void multiply_matrices_KIJ()
{
#pragma omp parallel 
    {
    for (int k = 0; k < SIZE; k++) {
#pragma omp for schedule(dynamic, 16) nowait
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
            }
        }
    }
    }
}

而且要清楚,我知道循环的顺序不同,我可以得到更好的结果,但事情就是这样 - 我必须使用KIJ命令。 我的任务是并行执行KIJ for循环并检查性能的提高。 我的问题是我期望(ed)至少快一点的执行速度(比我现在最快的速度快5到10%),即使它是并行的I循环(也不能用K循环,因为我将得到不正确的结果,因为它是matrix_r [i] [j])。

这些是我在使用上面显示的代码时获得的结果(我正在进行数百次计算并得到平均时间):

SIZE = 128

  • 串口版:0,000608s
  • 并行I,时间表(动态,16):0,000683s
  • 并行I,schedule(静态,16):0,000647s
  • 并行J,没有时间表:0,001978s(这是我执行速度较慢的地方)

SIZE = 256

  • 串行版本:0,005787s
  • 并行I,时间表(动态,16):0,005125s
  • 并行I,schedule(静态,16):0,004938s
  • 并行J,没有时间表:0,013916s

SIZE = 1024

  • 串行版本:0,930250s
  • 并行I,时间表(动态,16):0,865750s
  • 并行I,schedule(静态,16):0,823750s
  • 并行J,没有时间表:1,137000s

注意:这个答案不是关于如何从循环次序中获得最佳性能或如何并行化它,因为我认为由于几个原因它是次优的。 我将尝试提供一些关于如何改进订单(并将其并行化)的建议。

循环顺序

OpenMP通常用于在多个CPU上分配工作。 因此,您希望最大化每个线程的工作负载,同时最大限度地减少所需数据和信息传输的数量。

  1. 您希望并行执行最外层循环而不是第二个循环。 因此,您需要将其中一个r_matrix索引作为外部循环索引,以便在写入结果矩阵时避免竞争条件。

  2. 接下来就是你想要以内存存储顺序遍历矩阵(具有更快的变化索引作为第二个而不是第一个下标索引)。

您可以使用以下循环/索引顺序来实现这两个目的:

for i = 0 to a_rows
  for k = 0 to a_cols
    for j = 0 to b_cols
      r[i][j] = a[i][k]*b[k][j]

哪里

  • j变化快于ikk变化速度比i快。
  • i是一个结果矩阵下标, i循环可以并行运行

以这种方式重新排列multiply_matrices_KIJ已经提供了相当多的性能提升。

我做了一些简短的测试,我用来比较时间的代码是:

template<class T>
void mm_kij(T const * const matrix_a, std::size_t const a_rows, 
  std::size_t const a_cols, T const * const matrix_b, std::size_t const b_rows, 
  std::size_t const b_cols, T * const matrix_r)
{
  for (std::size_t k = 0; k < a_cols; k++)
  {
    for (std::size_t i = 0; i < a_rows; i++)
    {
      for (std::size_t j = 0; j < b_cols; j++)
      {
        matrix_r[i*b_cols + j] += 
          matrix_a[i*a_cols + k] * matrix_b[k*b_cols + j];
      }
    }
  }
}

模仿你的multiply_matrices_KIJ()函数

template<class T>
void mm_opt(T const * const a_matrix, std::size_t const a_rows, 
  std::size_t const a_cols, T const * const b_matrix, std::size_t const b_rows, 
  std::size_t const b_cols, T * const r_matrix)
{
  for (std::size_t i = 0; i < a_rows; ++i)
  { 
    T * const r_row_p = r_matrix + i*b_cols;
    for (std::size_t k = 0; k < a_cols; ++k)
    { 
      auto const a_val = a_matrix[i*a_cols + k];
      T const * const b_row_p = b_matrix + k * b_cols;
      for (std::size_t j = 0; j < b_cols; ++j)
      { 
        r_row_p[j] += a_val * b_row_p[j];
      }
    }
  }
}

实施上述订单。

英特尔i5-2500k上两个2048x2048矩阵相乘的时间消耗

  • mm_kij() :6.16706s。

  • mm_opt() :2.6567s。

给定的顺序还允许外部循环并行化,而不会在写入结果矩阵时引入任何竞争条件:

template<class T>
void mm_opt_par(T const * const a_matrix, std::size_t const a_rows, 
  std::size_t const a_cols, T const * const b_matrix, std::size_t const b_rows, 
  std::size_t const b_cols, T * const r_matrix)
{
#if defined(_OPENMP)
  #pragma omp parallel
  {
    auto ar = static_cast<std::ptrdiff_t>(a_rows);
    #pragma omp for schedule(static) nowait
    for (std::ptrdiff_t i = 0; i < ar; ++i)
#else
    for (std::size_t i = 0; i < a_rows; ++i)
#endif
    {
      T * const r_row_p = r_matrix + i*b_cols;
      for (std::size_t k = 0; k < b_rows; ++k)
      {
        auto const a_val = a_matrix[i*a_cols + k];
        T const * const b_row_p = b_matrix + k * b_cols;
        for (std::size_t j = 0; j < b_cols; ++j)
        {
          r_row_p[j] += a_val * b_row_p[j];
        }
      }
    }
#if defined(_OPENMP)
  }
#endif
}

每个线程写入单个结果行的位置

英特尔i5-2500k(4个OMP线程)上两个2048x2048矩阵相乘的时间消耗

  • mm_kij() :6.16706s。

  • mm_opt() :2.6567s。

  • mm_opt_par() :0.968325s。

不完美的缩放,但作为一个比串行代码更快的开始。

OpenMP实现创建了一个线程池(尽管OpenMP标准没有规定线程池,我已经看到过OpenMP的每个实现),因此每次输入并行区域时都不必创建和销毁线程。 然而,每个并行区域之间存在障碍,因此所有线程都必须同步。 并行区域之间的fork连接模型可能存在一些额外的开销。 因此,即使不必重新创建线程,它们仍然必须在并行区域之间进行初始化。 更多细节可以在这里找到。

为了避免进入并行区域,我建议建立在最外层循环的并行区域,但这样做的内循环工作共享过的开销, i喜欢这样的:

void multiply_matrices_KIJ() {
    #pragma omp parallel
    for (int k = 0; k < SIZE; k++)
        #pragma omp for schedule(static) nowait
        for (int i = 0; i < SIZE; i++)
            for (int j = 0; j < SIZE; j++)
                matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
}

使用#pragma omp for时存在隐含的障碍。 nowait子句删除了障碍。

还要确保使用优化进行编译。 在未启用优化的情况下比较性能几乎没有意义。 我会用-O3

请记住,出于缓存目的,循环的最佳排序将是最慢的 - >最快。 在你的情况下,这意味着我,K,L顺序。 如果您的序列代码没有被编译器从KIJ-> IKL排序中自动重新排序(假设您有“ -O3 ”),我会感到非常惊讶。 但是,编译器无法使用并行循环执行此操作,因为这会破坏您在并行区域中声明的逻辑。

如果你真的无法重新排序你的循环,那么你最好的选择可能是重写并行区域以包含最大可能的循环。 如果您有OpenMP 4.0,您也可以考虑在最快的维度上使用SIMD矢量化。 但是,由于KIJ订购中固有的上述缓存问题,我仍然怀疑你能够击败你的串行代码...

void multiply_matrices_KIJ()
{
    #pragma omp parallel for
    for (int k = 0; k < SIZE; k++)
    {
        for (int i = 0; i < SIZE; i++)
            #pragma omp simd
            for (int j = 0; j < SIZE; j++)
                matrix_r[i][j] += matrix_a[i][k] * matrix_b[k][j];
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM