C语言 循环展开对内存绑定数据的影响



我一直在使用一段大量内存限制的代码。我正在尝试通过手动实现缓存阻塞、软件预取、循环展开等来在单个内核中优化它。即使缓存阻塞可以显著提高性能。但是,当我引入循环展开时,我得到了巨大的性能下降。

我正在使用英特尔 icc 进行编译,在我的所有测试用例中都带有编译器标志 -O2 和 -IPO。

我的代码类似于这个(3D 25 点模板):

    void stencil_baseline (double *V, double *U, int dx, int dy, int dz, double c0, double c1,     double c2, double c3, double c4)
   {
   int i, j, k;
   for (k = 4; k < dz-4; k++) 
   {
    for (j = 4; j < dy-4; j++) 
    {
        //x-direction
            for (i = 4; i < dx-4; i++) 
        {
            U[k*dy*dx+j*dx+i] =  (c0 * (V[k*dy*dx+j*dx+i]) //center
                +  c1 * (V[k*dy*dx+j*dx+(i-1)] + V[k*dy*dx+j*dx+(i+1)])                 
                +  c2 * (V[k*dy*dx+j*dx+(i-2)] + V[k*dy*dx+j*dx+(i+2)])     
                +  c3 * (V[k*dy*dx+j*dx+(i-3)] + V[k*dy*dx+j*dx+(i+3)]) 
                +  c4 * (V[k*dy*dx+j*dx+(i-4)] + V[k*dy*dx+j*dx+(i+4)]));
        }
        //y-direction   
        for (i = 4; i < dx-4; i++) 
        {
            U[k*dy*dx+j*dx+i] += (c1 * (V[k*dy*dx+(j-1)*dx+i] + V[k*dy*dx+(j+1)*dx+i])
                + c2 * (V[k*dy*dx+(j-2)*dx+i] + V[k*dy*dx+(j+2)*dx+i])
                + c3 * (V[k*dy*dx+(j-3)*dx+i] + V[k*dy*dx+(j+3)*dx+i]) 
                + c4 * (V[k*dy*dx+(j-4)*dx+i] + V[k*dy*dx+(j+4)*dx+i]));
        }
        //z-direction
        for (i = 4; i < dx-4; i++) 
        {
            U[k*dy*dx+j*dx+i] += (c1 * (V[(k-1)*dy*dx+j*dx+i] + V[(k+1)*dy*dx+j*dx+i])
                + c2 * (V[(k-2)*dy*dx+j*dx+i] + V[(k+2)*dy*dx+j*dx+i])
                + c3 * (V[(k-3)*dy*dx+j*dx+i] + V[(k+3)*dy*dx+j*dx+i]) 
                + c4 * (V[(k-4)*dy*dx+j*dx+i] + V[(k+4)*dy*dx+j*dx+i]));
        }
    }
   }
 }

当我在最内层的循环(维度 i)上进行循环展开并分别按展开因子 2,4,8 在方向 x,y,z 上展开时,我在所有 9 种情况下都会得到性能下降,即在方向 x 上展开 2,在方向 y 上展开 2,在方向 z 上展开 2,在方向 x 上展开 4 ... 等。但是当我在最外层的循环(维度 k)上按系数 8(也是 2 和 4)执行循环展开时,我得到了 v.good 性能改进,这甚至比缓存阻塞更好。

我甚至尝试使用英特尔 Vtune 分析我的代码。这似乎是瓶颈,主要是由于 1.LLC 小姐和 2.LLC 负载未命中由远程 DRAM 提供服务。

我无法理解为什么展开最内层最快的循环会导致性能下降,而展开最外层、最慢的维度会提高性能。但是,后一种情况下的这种改进是当我在使用 icc 编译时使用 -O2 和 -ipo 时。

我不确定如何解释这些统计数据。有人可以帮助阐明这一点吗?

这强烈表明您正在通过展开导致指令缓存未命中,这是典型的。 在现代硬件时代,展开不再自动意味着更快的代码。 如果每个内部循环都适合缓存行,您将获得更好的性能。

您可以手动展开,以限制生成的代码的大小,但这需要检查生成的机器语言指令及其位置,以确保循环位于单个缓存行内。 缓存行的长度通常为 64 字节,并在 64 字节边界上对齐。

外循环没有相同的效果。 无论展开级别如何,它们都可能位于指令缓存之外。 展开这些结果会减少分支,这就是获得更好性能的原因。

"远程 DRAM 服务的负载未命中"表示您在一个 NUMA 节点上分配了内存,但现在您在另一个节点上运行。 基于 NUMA 设置进程或线程相关性就是答案。

远程 DRAM 读取时间几乎是我使用过的英特尔机器上的本地 DRAM 的两倍。

相关内容

  • 没有找到相关文章

最新更新