使用速记 IF 的 C++ 编译器优化

Posted

技术标签:

【中文标题】使用速记 IF 的 C++ 编译器优化【英文标题】:C++ compiler optimization with shorthand IF 【发布时间】:2012-02-23 17:15:47 【问题描述】:

当涉及到简写 if/else 时,我想到了一个关于编译器优化的问题。

我有这个功能:

double eu_distance (const coor& x, const coor& y) 

return ((y.x - x.x)*(y.x - x.x) + (y.y - x.y)*(y.y - x.y));

我想知道什么更有效?

min = min > eucl_distance(point_a, point_b) ? eucl_distance(point_a, point_b) : min;

double dis = eucl_distance(point_a, point_b);
if (min > dis)
    min = dis;

在前一种情况下,编译器(在我的情况下是 GCC 4.6.2)是否知道如何优化 if/else 以保持 eucl_distance() 的返回值以重复使用而不是计算两次?

一个附带的问题是:

什么更有效?

(y.x - x.x)*(y.x - x.x)

pow((y.x - x.x),2)

PS:对不起,我只能选择一个正确的答案! :( 谢谢大家的回答!我真的很感激他们!

【问题讨论】:

简介!简介!简介!简介!简介!简介!是唯一的答案。 我添加了 gcc 作为标签,因为答案可能是编译器特定的。 如果有疑问,您可以随时编译代码并手动比较程序集。即使您不知道汇编,如果代码相同,那么您就知道它会针对相同的事物进行优化。但请记住,过早的优化是万恶之源。一般来说,这种优化对你根本没有帮助,除非你已经跟踪了程序并找到了热路径(并且没有更合适的算法)。 查看编译后的输出,或对数百万次迭代的循环进行时间/分析。这将是非常特定于编译器的。 理论上它们是一样的,但是 pow() 会慢一些,因为当函数被调用时,它需要被放入堆栈并创建你传递给它的数据的副本。话虽这么说,速度上的差异是如此之小,最好只使用 pow ,因为当你获得更高的权力时,你不会有类型 xxxxx . 【参考方案1】:

没有通用的答案:您必须分析生成的代码 由您的实施确定。然而,在大多数情况下,如果 eu_distance 在单独的翻译单元中,并不特别 注释,编译器将无法知道调用它两次 使用相同的论点将给出相同的结果;在这种情况下, 第二种形式几乎肯定会更快。另一方面,如果 eu_distance 可以内联,任何体面的优化器都会结束 为两者生成几乎完全相同的代码。

实际上,我几乎肯定会使用第三种形式:

min = std::min( eu_distance( point_a, point_b ), min );

(我假设eucl_distanceeu_distance 的拼写错误。)

另外,我会避免使用像 min 这样的名称。有人太可能添加 using namespace std; 之后,甚至包括 <windows.h>,没有 已定义NOMINMAX。 (<windows.h>minmax 定义为 如果 NOMINMAX 尚未定义,则为宏。这导致一些 如果您定义自己的minmax,则会出现有趣的错误消息。或者 甚至包括<algorithm>。)

关于pow( x, 2 ):再一次,你真的需要测量,但是 通常,x * x 会更快,即使 x 是一个复杂的 表达。 (当然,如果表达式不平凡,那么 认识到x 是相同的可能不是那么容易,这 使代码更难阅读。在这种情况下,您可能希望 考虑一个小函数,比如squared,它除了返回什么都不做 x * x。如果它对性能有影响,则内联它。)

【讨论】:

【参考方案2】:

所有关于优化器如何处理代码的问题都困难,因为有许多变量会影响优化器。答案的第一部分是缓存函数的结果并重用它在任何情况下都不会比替代方法差,所以我会继续使用第二种方法。

至于第一种方法可以优化什么,这取决于代码的布局方式。如果编译器可以访问euclid_distance 的定义并且可以内联它,那么它可以有效地确定该函数是pure 并且将在第二次调用,因此它可能会缓存第一次调用的值。如果编译器无权访问函数的定义,那么它无法知道对函数的第一次和第二次调用是否会产生相同的结果(考虑rand(),每次调用都会产生不同的数字),所以它会调用该函数两次。您可以通过 标记 函数为编译器提供帮助。您可以在 gcc 中搜索 __pure 属性,如果我没记错的话,它将在这种情况下对优化器有所帮助。

关于使用pow 与普通乘法,再次,对于2 的幂,我将只使用直接乘法。 pow 基本上不可能让它更高效。在这种情况下,您不需要缓存y.x-x.x,因为编译器看到它被重用并且可以为您执行此操作。

【讨论】:

【参考方案3】:

嗯,理论上是这样的:

double dis = eucl_distance(point_a, point_b);
if (min > dis)
    min = dis;

比这样更有效率:

min = min > eucl_distance(point_a, point_b) ? eucl_distance(point_a, point_b) : min;

...仅仅因为您消除了额外的呼叫eucl_distance(point_a, point_b)

但是如果你将你的函数标记为“纯”,或者如果编译器可以看到定义并确定它是纯的,它将消除对它的额外调用,就像它消除重复的数学表达式一样,然后生成代码将几乎相同。但是,你不能总是去检查组装等,所以我会坚持第一种情况,这样更干净。

至于您的背负问题,(y.x - x.x)*(y.x - x.x) 更快(您可以更具体并将y - x 的结果存储在 tmp 变量中)...但是,编译器很清楚pow 函数,因为第二个参数是编译时表达式,它可以轻松展开该代码以匹配您手动编码的乘法。但是同样,你不能真正依赖你,所以如果你能保持代码更清晰、更明显——那就去做吧。例如,我看不出您有任何理由要致电 pow(something, 2)

永远记住,过早的优化是不好的。但是,一开始也不要编写蹩脚的慢代码:)

【讨论】:

【参考方案4】:

我会说:

不要假设任何编译器行为。 重用结果以避免不必要的调用

没有任何东西告诉编译器你的 eucl_distance 函数是完全幂等的。所以假设缓存结果它没有机会跳过额外的调用。 (如果你在调用中增加一个静态计数器变量呢???)

对于乘法与 pow 的关系,我猜它取决于底层处理器数学优化。

【讨论】:

以上是关于使用速记 IF 的 C++ 编译器优化的主要内容,如果未能解决你的问题,请参考以下文章

优化C++软件

优化C++软件

优化C++软件

C++:编译器能否优化按值传递?

为啥 C++ 编译器不优化对结构数据成员的读写而不是不同的局部变量?

C++:指针和编译器(别名?)优化