簡體   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