我一直在使用一段密集内存的代码。我试图通过手动实现缓存阻塞,sw预取,循环展开等来在单个核心内优化它。即使缓存阻塞可以显着提高性能。然而,当我引入循环展开时,我会遇到巨大的性能下降。
在我的所有测试用例中,我正在使用带有编译器标志-O2和-ipo的Intel icc进行编译。
我的代码与此类似(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种情况下都会降低性能,即在方向上展开2 x,在y方向展开2,在z方向展开2,在x方向展开4 ...等。 但是当我在最外面的循环(维度k)上执行循环展开因子为8(也是2和4)时,我得到的v.good性能改进甚至比缓存阻塞更好。
我甚至尝试使用英特尔Vtune分析我的代码。这似乎是瓶颈,主要是由于1.LLC Miss和2. LLC加载错过了远程DRAM服务。
我无法理解为什么展开最里面最快的循环以提供性能下降,而展开最外层,最慢的维度是为了提升性能。但是,后一种情况的改进是在使用icc编译时使用-O2和-ipo。
我不确定如何解释这些统计数据。有人可以帮助阐明这一点。
答案 0 :(得分:1)
这强烈暗示您通过展开导致指令缓存未命中,这是典型的。在现代硬件时代,展开不再自动意味着更快的代码。如果每个内部循环都适合缓存行,您将获得更好的性能。
您可以手动展开,以限制生成代码的大小,但这需要检查生成的机器语言指令及其位置,以确保您的循环位于单个缓存行中。高速缓存行通常为64字节长,并在64字节边界上对齐。
外环不具有相同的效果。无论展开级别如何,它们都可能位于指令缓存之外。将这些结果展开为更少的分支,这就是为什么您获得更好的性能。
“远程DRAM服务的加载未命中”意味着您在一个NUMA节点上分配了内存,但现在您正在另一个NUMA节点上运行。基于NUMA设置进程或线程亲和性就是答案。
远程DRAM在我使用过的英特尔机器上读取的时间几乎是本地DRAM的两倍。