C语言中的整数幂

7
在C代码中,通常会写入以下内容:
a = b*b;

替代

a = pow(b, 2.0);

针对double变量的情况,由于pow是一个通用函数,能够处理非整数指数,因此很多人可能会认为第一个版本比较快。然而我想知道编译器(gcc)是否会将带有整数指数的pow调用转换为直接乘法作为可选优化的一部分。

假设这种优化不会发生,那么哪个最大的整数指数可以通过手动写成类似于b*b* ... *b的乘法来使计算速度更快呢?

我知道我可以在特定的机器上进行性能测试以确定是否需要关注这个问题,但我想更深入地了解什么才是“正确的做法”。


1
架构相关。 - Martin James
2
pow()(我猜您指的是这个函数)不仅仅是一个能够处理非整数指数的函数;它是一个接受double类型参数并返回double类型的函数。虽然这是一个有些微妙的点,但参数和返回值的类型与它们可能采用的值一样重要。 - John Bollinger
2
编译器不会进行此转换,因为 power 处理的是浮点数值。对于计算整数幂,手动展开 b*b ... *b 几乎总是更快的。 - Cody Gray
1
这很可能是 这个问题 的重复,只不过你明确地询问整数,而那个问题的大多数答案都假定浮点数。然而,答案是相同的:手动写出乘法更快,因为库函数必须足够通用以处理 所有 可能的情况。这对整数和浮点值都是正确的,但对于整数尤其如此,因为 FP 转换非常缓慢。 - Cody Gray
5
有趣的是,在x86-64上,gccclang似乎会将pow(b, 2.0)转换为b * b,但b仍然被视为double类型。(godbolt参考链接)观察mulsd %xmm0,%xmm0指令。但是,当用3.0替换2.0时,相同的优化似乎不会发生。 - Ben Steffan
显示剩余5条评论
2个回答

2
你需要的是 -ffinite-math-only -ffast-math,可能还需要包含#include <tgmath.h>。这与强制使用-O3优化的-Ofast相同。

启用-ffinite-math-only和-ffast-math不仅有助于这些类型的优化,而且类型通用数学函数还有助于补偿当你忘记为(非双精度)数学函数附加适当后缀时的问题。

例如:

#include <tgmath.h>
float pow4(float f){return pow(f,4.0f);}
//compiles to
pow4:
    vmulss  xmm0, xmm0, xmm0
    vmulss  xmm0, xmm0, xmm0
    ret

对于clang,它可以处理32位以下的次方数,而gcc则至少可以处理2,147,483,647次方(这是我检查到的最大值),除非启用-Os选项(因为跳转到pow()函数更小)-使用-Os时,将仅进行2的幂次方计算。

警告:-ffast-math只是几个其他优化的快捷别名,其中许多会破坏各种标准。如果您希望只使用最小的标志来获得此期望的行为,则可以使用-fno-math-errno -funsafe-math-optimizations -ffinite-math-only


0
在考虑正确的事情时,请考虑您的维护者而不仅仅是性能。我有一种预感,您正在寻找一个通用规则。如果您正在进行简单且一致的数字平方或立方运算,则不建议使用pow函数。pow函数很可能会进行某种形式的子程序调用,而不是执行寄存器操作(这就是为什么Martin指出了架构依赖性)。

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