缓存模块化技术

面向英特尔® MIC 架构的编译器方法

缓存模块化技术

概述

一类重要的算法变化涉及模块数据结构以便能够适应高速缓存。通过合理组织数据内存访问,用户可以使用大型数据集中的一小部分来加载高速缓存。此方法也适用于缓存中的该数据模块。通过使用/重复使用高速缓存中的数据,我们可显著降低内存访问需求(减少内存带宽压力)。
主题

模块化是一种非常著名的优化技术可在许多应用中帮助避免内存带宽瓶颈。模块化的关键思路是确保数据在多种使用情形下都位于高速缓存中,从而尽可能地重复利用应用中的数据。模块化可在 1-D2-D 3-D 空间数据结构上执行。某些迭代应用可以从多次迭代操作(通常被称为临时模块化)的模块化获益,并进一步减少带宽瓶颈。代码变更方面,模块化通常涉及循环分割与变换的组合。在大多数应用代码中,如果用户能够做出正确的源代码调整,并对模块化因素进行一定的参数化处理,便可取得最佳的模块化执行效果。
原始源代码:

for (body1 = 0; body1 < NBODIES; body1 ++){

 for (body2=0; body2 < NBODIES; body2++) {

    OUT[body1] += compute(body1, body2);

 }

}

在本例中数据 (body2) 从内存流出。假设 NBODIES 较大我们不能在高速缓存中重复使用 => 该应用受内存带宽的限制应用将以内存到处理器的速度运行这并不是最理想的
修改源代码 1-D 模块化):

for (body2 = 0; body2 < NBODIES; body2 += BLOCK) {

   for (body1=0; body1 < NBODIES; body1 ++) {

       for (body22=0; body22 < BLOCK; body22 ++) {

           OUT[body1] += compute(body1, body2 + body22);

       }

   }

}

在修改后的代码中高速缓存中保留了数据 (body22) 并可以重复使用 => 更高的性能

例如上面的代码片段显示了模块化 NBody 代码的示例。所有主体部分都有两个循环(body1 body2)进行迭代顶部数据流上的原始代码到内部循环中的整个主体集,而且每次迭代时必须从内存加载 body2。底部的模块化代码通过将 body2 分割为一个外部循环(在多个 BLOCK 的主体上进行迭代)、一个内部 body22(迭代模块内的相关元素)循环,并将 body1 body2 循环进行交叉而获得。该代码可重复使用跨 body1 循环的多个迭代的一组 BLOCK body2 值。如果所选择的 BLOCK(例如这一组值)适合高速缓存,内存流量便会下降,具体取决于 BLOCK 因素。

以下是 NBody 性能指标评测 OpenMP* 版的相关代码片段带有使用 CHUNK_SIZE 因素的模块化 。在本例中,unroll-jam 转换表示为编译指令,并由编译器负责处理。 在这些情况下,研究来自-opt-报告的输出可证实该编译器是否针对您的循环执行了 unroll-jam 优化。

#define CHUNK_SIZE 8192

#pragma omp parallel private(body_start_index)
  for(body_start_index = 0; body_start_index < global_number_of_bodies; body_start_index += CHUNK_SIZE){
    int i;
    int body_end_index = body_start_index + CHUNK_SIZE;

    #pragma omp for private(i) schedule(guided)
    #pragma unroll_and_jam (4)
    for(i=starting_index; i<ending_index; i++){
      int j;
      TYPE acc_x_0 = 0, acc_y_0 = 0, acc_z_0 = 0;
      for(j=body_start_index; j<body_end_index; j+=1){
        TYPE delta_x_0 = Input_Position_X[(j+0)] - Input_Position_X[i];
        TYPE delta_y_0 = Input_Position_Y[(j+0)] - Input_Position_Y[i];
        TYPE delta_z_0 = Input_Position_Z[(j+0)] - Input_Position_Z[i];

        TYPE gamma_0 = delta_x_0*delta_x_0 + delta_y_0*delta_y_0 + delta_z_0*delta_z_0 + epsilon_sqr;
        TYPE s_0 = Mass[j+0]/(gamma_0 * SQRT(gamma_0));
        acc_x_0 += s_0*delta_x_0;
        acc_y_0 += s_0*delta_y_0;
        acc_z_0 += s_0*delta_z_0;
      }
      Output_Acceleration[3*(i+0)+0] += acc_x_0;
      Output_Acceleration[3*(i+0)+1] += acc_y_0;
      Output_Acceleration[3*(i+0)+2] += acc_z_0;
    }
  }

下面的示例为 Fortran 中的矩阵乘法代码用户在其中执行高级转换在修改版本中),其中涉及本地复制-数组以实现最高的性能。


Fortran
源代码示例

do j=1,N

  do k = 1,N

    do i = 1,N

      c(i,j) = c(i,j) + a(i,k) * b(k,j)

    end do

  end do

end do

修改后的 Fortran 源代码:

     do JJ = 1, N, TJ

       do KK = 1, N, TK

         do jjj = 1,min(tj,N-jj+1)                     ! BCOPY - no transpose

           do kkk = 1, min(tk,N-kk+1)

             p(kkk,jjj-1+1) = B(kk+kkk-1, jj+jjj-1)

           end do

         end do

         do II = 1, N, TI

           do iii = 1,

             min(ti,N-ii+1)                   !ACOPY - transpose

             do kkk = 1, min(tk,N-kk+1)

                Q(kkk,iii) = A(ii+iii-1, kk+kkk-1)

             end do

           end do

           do J = 1, min(tj,N-jj+1), 4

             do I = 1, min(ti,N-ii+1), 2

                t1 = 0 ; t2 = 0 ; t5 = 0 ; t6 = 0 ; t9 = 0 ; t10 = 0 ; t13 =0 ; t14 = 0

                !DIR$ vector aligned                      !DIR$ unroll(2)

                do K = 1,min(TK,N-kk+1)      ! Innermost loop, vectorized and unrolled by 2 after that

                   qi = Q(K,I)           ;    qi1 = Q(K,I+1) 

                   t1 = t1+qi*P(K,J)     ;    t2 = t2+ qi1*P(K,J)

                   t5 = t5+ qi*P(K,J+1)  ;    t6 = t6+ qi1*P(K,J+1)

                   t9 = t9+ qi*P(K,J+2)  ;    t10 = t10+ qi1*P(K,J+2)

                   t13 = t13+ qi*P(K,J+3);    t14 = t14+qi1*P(K,J+3)

                end do

               c(i+ii-1,j+jj-1) = c(i+ii-1,j+jj-1) +t1          ; c(i+1+ii-1,j+jj-1) = c(i+1+ii-1,j+jj-1) + t2

               c(i+ii-1,j+1+jj-1) = c(i+ii-1,j+1+jj-1) + t5     ; c(i+1+ii-1,j+1+jj-1) = c(i+1+ii-1,j+1+jj-1) + t6

               c(i+ii-1,j+2+jj-1) = c(i+ii-1,j+2+jj-1) + t9     ; c(i+1+ii-1,j+2+jj-1) = c(i+1+ii-1,j+2+jj-1) + t10

               c(i+ii-1,j+3+jj-1) = c(i+ii-1,j+3+jj-1) + t13    ; c(i+1+ii-1,j+3+jj-1) = c(i+1+ii-1,j+3+jj-1) + t14

             end do

           end do

         end do

       end do

     end do

Take Aways

高速缓存模块化是一种重新安排数据访问的技术它能够将数据子集模块放入高速缓存中以便在该模块上运行进而避免从主内存重复预取数据。正如上面的示例所示,用户可以这种方法手动实现循环数据的模块化以重复使用高速缓存。对于性能关键型循环,性能分析指出了内存带宽限制,而且 -opt- 报告显示编译器不能以最优的方式对循环进行模块化,您可能要考虑手动展开循环,以便更好的进行数据模块化处理进而重复使用高速缓存。


下一步工作

要在英特尔® 至强 融核协处理器上成功调试您的应用请务必通读此指南并点击文中的超链接查看相关内容。本指南提供了实现最佳应用性能所要执行的步骤。

返回为英特尔® 集成众核架构做好准备

Per informazioni più dettagliate sulle ottimizzazioni basate su compilatore, vedere il nostro Avviso sull'ottimizzazione.