运营商的C地址非常慢

时间:2012-01-28 18:07:58

标签: c performance reference mpi operator-keyword

我正在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秒。所以看起来它是专门获取导致速度放缓的maxdiffdiff的地址。

我通过将两个额外的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

4 个答案:

答案 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);

这只是一种理论。