【问题标题】:C address-of operator very slowC地址运算符非常慢
【发布时间】:2012-01-28 18:07:58
【问题描述】:

我正在用 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

【问题讨论】:

  • 请注意,“&”是“address-of”运算符,而不是“reference”运算符。
  • 我认为“address-of”和“reference”运算符是一回事。与“取消引用”运算符* 相反。不过我可能是错的。
  • 注意:您在每次迭代时将 maxdiff 设置为 0.0。我希望在do { 行之前看到maxdiff = 0.0
  • maxdiffdiff 定义在哪里?它们是局部变量还是全局变量?如果它们是全局的,如果将它们设为静态会发生什么?
  • 函数调用的时钟计数可能在 10 到 100 次之间。在两个版本中。 inside 函数会发生什么更昂贵(向 all slaves 发送/接收消息)加上等到 all 已回答。我建议使用 -pg 进行编译(如果您使用的是 gcc)并在其上运行 gprof。

标签: c performance reference mpi operator-keyword


【解决方案1】:

这对我来说听起来有点不太可能。获取某个 double 的地址实际上应该相当快。

如果你仍然怀疑,那只获取一次地址怎么样?

double maxdiff, diff;
double *pmaxdiff = &maxdiff, *pdiff = &diff;

...

MPI_Allreduce(pmaxdiff, pdiff, 1, MPI_DOUBLE, MPI_MAX, my_comm);

...

总体而言,我怀疑减速发生在其他地方,但请尝试一下。

【讨论】:

  • 我知道,我也觉得不太可能。然而,事实是,使用临时发送和接收缓冲区,程序在 225 秒内运行,而没有它们,它在 290 秒内运行。我没有改变其他任何东西。
【解决方案2】:

我建议查看生成的程序集和/或在此处发布。

您可以使用gcc -S <source> 获取程序集

【讨论】:

  • 针对您的评论,我确实分析了程序集。我不是汇编专家,但我看到的主要区别是快速版本似乎使用专门的浮点指令,而慢速版本没有。这让我想到了另一种可能性,即地址操作符的使用实际上阻止了编译器进行一些优化,它可以通过在那里添加的发送/接收缓冲区来实现。
  • @JackRyan 如何在地址计算中使用 FP 指令?你能说是哪些吗?
【解决方案3】:

我建议对 MPI 程序使用性能分析工具 [1],以更好地了解正在发生的事情。我猜想在您的代码的不同版本中延长的是实际的MPI_Allreduce 调用,而不是地址计算。正如您所提到的,内存在这里很重要 - 因此 PAPI 计数器(缓存未命中等)可能会提示该问题。

[1] 如:

【讨论】:

    【解决方案4】:

    我怀疑不是地址是问题,而是地址最终是什么。这就是我的意思。

    我假设您的代码在调用MPI_Allreduce 之前不会触及diff 变量,而diff 恰好位于与其他变量不同的缓存行上。由于问题的数据量很大,包含diff 的缓存行在调用时被从缓存中逐出。现在MPI_Allreducediff 执行写入操作。 Intel 的 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);
    

    这只是一个理论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-31
      • 2022-01-23
      • 1970-01-01
      • 1970-01-01
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多