我正在C中实现一个MPI程序,它在网格上执行SOR(连续过度放松)。在对它进行基准测试时,我遇到了一些非常意外的事情,即运算符&
的地址似乎非常慢。我不能在这里显示整个代码,而且它也太长了,但相关部分如下。
double maxdiff, diff;
do {
maxdiff = 0.0;
/* inner loops updating maxdiff a lot */
/* diff is used as a receive buffer here */
MPI_Allreduce(&maxdiff, &diff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
maxdiff = diff;
} while(maxdiff > stopdiff);
在这里,stopdiff是一些神奇的价值。行为缓慢出现在MPI_Allreduce()
操作中。奇怪的是,即使在这种情况下不需要通信,在仅在单个节点上运行时该操作甚至非常慢。当我将操作注释掉时,一个节点上特定问题的运行时间从290秒减少到225秒。此外,当我使用其他伪造变量用MPI_Allreduce()
调用替换操作时,我也得到225秒。所以看起来它是专门获取导致速度放缓的maxdiff
和diff
的地址。
我通过将两个额外的double
变量用作临时发送/接收缓冲区来更新程序,如下所示。
send_buf = maxdiff;
MPI_Allreduce(&send_buf, &recv_buf, 1, MPI_DOUBLE, MPI_MAX, my_comm);
maxdiff = recv_buf;
这也让程序运行225秒而不是290秒。我的问题显然是,这怎么可能?
我确实怀疑:程序是使用优化级别为O3的gcc编译的,所以我怀疑编译器正在进行一些优化,这使得参考操作非常慢。例如,可能变量存储在cpu寄存器中,因为它们经常在循环中使用,因此,只要它们的地址被请求,就必须将它们刷回内存。但是,我似乎无法通过谷歌搜索发现什么样的优化可能会导致这个问题,我想确定问题。有人知道可能导致这种情况的原因吗?
提前致谢!
我应该在这里添加一些其他重要信息。正在运行的具体问题填补了内存非常糟糕的问题。它使用3GB内存,节点总共有4GB RAM。我还观察到,随着RAM填满,较大问题大小的减速会变得更糟,因此RAM上的负载量似乎是问题的一个因素。另外,奇怪的是,当我在循环之后添加MPI_Allreduce()
而不是循环内部时,在程序的非优化版本中仍然存在减速,并且它仍然同样糟糕。该程序运行速度不快。
根据下面的要求,这是gcc程序集输出的一部分。不幸的是,我没有足够的装配经验来从中收集问题。这是添加了发送和接收缓冲区的版本,因此版本运行时间为225秒而不是290。
incl %r13d
cmpl $1, %r13d
jle .L394
movl 136(%rsp), %r9d
fldl 88(%rsp)
leaq 112(%rsp), %rsi
leaq 104(%rsp), %rdi
movl $100, %r8d
movl $11, %ecx
movl $1, %edx
fstpl 104(%rsp)
call MPI_Allreduce
fldl 112(%rsp)
incl 84(%rsp)
fstpl 40(%rsp)
movlpd 40(%rsp), %xmm3
ucomisd 96(%rsp), %xmm3
jbe .L415
movl 140(%rsp), %ebx
xorl %ebp, %ebp
jmp .L327
以下是我认为没有额外发送和接收缓冲区的程序中的相应部分,因此版本在290秒内运行。
incl %r13d
cmpl $1, %r13d
jle .L314
movl 120(%rsp), %r9d
leaq 96(%rsp), %rsi
leaq 88(%rsp), %rdi
movl $100, %r8d
movl $11, %ecx
movl $1, %edx
call MPI_Allreduce
movlpd 96(%rsp), %xmm3
incl 76(%rsp)
ucomisd 80(%rsp), %xmm3
movsd %xmm3, 88(%rsp)
jbe .L381
movl 124(%rsp), %ebx
jmp .L204
答案 0 :(得分:3)
这对我来说听起来有点不太可能。获得一些双倍的地址实际上应该相当快。
如果您仍然怀疑它,那么只获取一次地址怎么样?
double maxdiff, diff;
double *pmaxdiff = &maxdiff, *pdiff = &diff;
...
MPI_Allreduce(pmaxdiff, pdiff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
...
总的来说,我怀疑其他地方会发生减速,但试一试。
答案 1 :(得分:0)
我建议查看生成的程序集和/或在此处发布。
您可以使用gcc -S <source>
答案 2 :(得分:0)
我建议为MPI程序使用性能分析工具[1],以便更好地了解正在发生的事情。我猜这是在代码的不同版本中延长的实际MPI_Allreduce
调用,而不是地址计算。正如您所提到的,内存在这里至关重要 - 因此PAPI计数器(缓存未命中等)可能会提示该问题。
[1]如:
答案 3 :(得分:0)
我怀疑这不是解决问题的地址,而是该地址的最终结果。这就是我的意思。
我假设您的代码在diff
调用之前未触及MPI_Allreduce
变量,并且diff
恰好位于与其他变量不同的缓存行中。由于问题的数据大小很大,因此在调用时将包含diff
的缓存行从缓存中逐出。现在MPI_Allreduce
执行对diff
的写入。英特尔的CPU使用写分配策略,这意味着在写入之前,它们将执行读取以将该行带入缓存。
另一方面,临时变量可能与本地使用的其他东西共享一个缓存行。写入不会导致高速缓存未命中。
请尝试以下操作:替换
MPI_Allreduce(&maxdiff, &diff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
maxdiff = diff;
与
MPI_Allreduce(MPI_IN_PLACE, &maxdiff, 1, MPI_DOUBLE, MPI_MAX, my_comm);
这只是一种理论。