GCC是否可以将isnan(x) || isnan(y)优化为isunordered(x, y)?

20

这是我的代码:

int f(double x, double y)
{
  return std::isnan(x) || std::isnan(y);
}

如果您使用C而不是C ++,只需将 std :: 替换为 __builtin_ (不要简单地删除 std :: ,原因请参见此处:为什么GCC为C ++<cmath>比C<math.h> 更有效地实现isnan()?)。
这是汇编代码:
ucomisd %xmm0, %xmm0 ; set parity flag if x is NAN
setp    %dl          ; copy parity flag to %edx
ucomisd %xmm1, %xmm1 ; set parity flag if y is NAN
setp    %al          ; copy parity flag to %eax
orl     %edx, %eax   ; OR one byte of each result into a full-width register

现在让我们尝试一种相同效果的替代配方:

int f(double x, double y)
{
  return std::isunordered(x, y);
}

这是该替代方案的汇编代码:
xorl    %eax, %eax
ucomisd %xmm1, %xmm0
setp    %al

这很棒——我们几乎将生成的代码减少了一半!这是因为 ucomisd 如果其操作数之一是 NAN,则设置奇偶标志位,因此我们可以同时测试两个值,类似于SIMD的方式。
你可以在野外看到像原始版本那样的代码,例如:https://svn.r-project.org/R/trunk/src/nmath/qnorm.c 如果我们能让GCC足够聪明,在任何地方都能组合两个isnan()调用,那就太酷了。我的问题是:我们能吗?怎么做?我有一些关于编译器工作原理的想法,但不知道在GCC中这种优化可以在哪里执行。基本思路是每当有一对OR'd together的isnan()(或__builtin_isnan)调用时,它应该使用同时使用两个操作数的单个ucomisd指令进行发出。
编辑以添加Basile Starynkevitch答案激发的一些研究:
如果我使用-fdump-tree-all进行编译,我会找到两个似乎相关的文件。首先,*.gimple 包含以下内容(还有更多):
D.2229 = x unord x;
D.2230 = y unord y;
D.2231 = D.2229 | D.2230;

在这里,我们可以清楚地看到GCC知道它将(x, x)传递给isunordered()。如果我们想要在此级别上通过转换进行优化,规则大致为:“用a unord b替换a unord a | b unord b。” 这就是编译我的第二个C代码时得到的结果:

D.2229 = x unord y;

另一个有趣的文件是 *.original

return <retval> = (int) (x unord x || y unord y);

这实际上是由-fdump-tree-original生成的整个非注释文件。对于更好的源代码,它看起来像这样:

return <retval> = x unord y;

显然,同样的转换可以应用于这里(只不过这里使用||而不是|)。

但遗憾的是,如果我们修改源代码如下:

if (__builtin_isnan(x))
  return true;
if (__builtin_isnan(y))
  return true;
return false;

然后我们得到了完全不同的Gimple和原始输出文件,尽管最终汇编代码与以前相同。因此,也许在管道的后期尝试这种转换更好?在带有“if”的版本和原始版本中,“*.optimized”文件(其中之一)显示相同的代码,因此这是一个很好的迹象。

1
当然这是可能的 - 但这并不意味着在考虑到额外的复杂性、开销、需要维护的代码以及优化使用的频率等因素后,这是值得推荐的。无论如何,建议向GCC开发人员提出这个想法肯定是下一步考虑它的方法,而不是在这里发布。 - Tony Delroy
1
@TonyD:如果你认识一个愿意、有能力并且有时间来实现这个的GCC开发人员,请一定把这个传达给他们,或者告诉我他们的电子邮件地址,我会去联系他们。否则,问题就是关于我是否可以自己完成它而不需要过多的努力(我知道这样的事情的学习曲线非常陡峭)。已经有一个相关的、有用的答案在这里发布了,它教会了我一些东西,如果仅仅将其作为GCC bug提交,我是不会学到的。 - John Zwinck
2
在gcc-5中,可能只需要在其中一个.pd文件中使用(simplify (or (unordered @0 @0) (unordered @1 @1)) (unordered @0 @1))这样简单的代码(好吧,最后一个版本可能不是这样,因为有if)。请提交PR。 - Marc Glisse
@MarcGlisse:我已经在https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63387上提交了,并附上了你对于GCC 5的建议,谢谢。 - John Zwinck
2个回答

10

3
很高兴您抽出时间进行简化。 - Nils Pipenbrinck
1
它也在Clang 3.7+中。 - John Zwinck

3

有两个问题:

  • 您提出的优化在严格的C++11标准中是否始终合法(我不知道)。

  • 可以通过添加这样的优化来自定义GCC吗: 可以!您可以使用MELT扩展它-例如编写自己的MELT扩展程序,或者使用C++痛苦地编写自己的GCC插件。

但是,在GCC中添加额外的优化是一项重大工作(即使使用MELT),您需要了解GCC的内部。因此,这可能需要超过一周的时间。

而且我不确定这样的优化是否真的值得这个努力。


谢谢,我看到你是MELT的作者。在我理解之前,需要做一些工作,但我已经运行了 gcc -fdump-tree-all 并将我的一些发现编辑到问题中了。 - John Zwinck
我尝试构建MELT 1.0(用于GCC 4.7),但遇到了很多麻烦。首先,我需要比我的系统更新的“unifdef”(我的不支持“-o”,你可能可以在你的构建系统中克服这个问题)。然后,在melt.so中有一个未定义的引用指向libgmp中的某些内容,所以我将其添加到了Makefile中。然后它抱怨缺少“meltbuild-stage0-quicklybuilt/warmelt-first+meltdesc.c”,所以我进行了make clean,现在我得到了“melt-runtime.h:675:24: fatal error: meltrunsup.h: Too many levels of symbolic links”。 - John Zwinck
我也尝试使用GCC 4.9构建MELT 1.1。我不得不在链接行中添加“-lrt”以供“melt.so”使用(否则会出现对“clock_gettime”的未定义引用),然后我遇到了关于“warmelt-first+meltdesc.c”的相同错误。接下来,我因为你依赖于“pstree -s”,而我的系统的“pstree”没有该选项而出现了错误。我正在使用Ubuntu 10.04。抱歉,这是一个旧系统,我无能为力。我的另一个系统是Mac,它使用Clang。:( - John Zwinck
请至少使用MELT 1.1.2(或最新的快照),并在gcc-melt@googlegroups.com上报告错误 - 并订阅该邮件列表。 - Basile Starynkevitch
我认为使用一些不是每个人都能够访问的“功能”来重新构建和/或维护代码是一个非常糟糕的想法。 - user3629249

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接