为什么 math.Pow 性能比位移位差?

dev*_*dev 2 bit-shift go compiler-optimization

在 Exercism 网站上解决这个练习时,我使用了标准的 math.Pow 包函数来获得 2 的提升幂。

return uint64(math.Pow(2, float64(n-1)))
Run Code Online (Sandbox Code Playgroud)

在查看了社区解决方案后,我找到了一个使用位移实现相同功能的解决方案:

return uint64(1 << uint(n-1)), nil
Run Code Online (Sandbox Code Playgroud)

让我感到惊讶的是,两者之间存在很大的性能差异: 位移 math-pow

我认为 Go 编译器会识别出 math.Pow 使用常数 2 作为基础,并且只使用位移位,而我没有明确这样做。我能看到的唯一其他区别是 float64 和 math.Pow 对浮点数而不是整数的转换。

为什么编译器不优化功率运算来实现类似于位移的性能?

Pau*_*kin 7

首先,请注意这uint64(1) << (n-1)uint64(1 << uint(n-1))您问题中出现的表达式的更好版本。表达式1<<n是 an int,因此有效的移位值介于 0 和 30 或 62 之间,具体取决于 int 的大小。uint64(1) << n允许n在 0 到 63 之间。

一般来说,您建议的优化是不正确的。编译器必须能够推断出n在特定范围内。

看这个例子(在操场上)

package main

import (
    "fmt"
    "math"
)

func main() {
    n := 65
    fmt.Println(uint64(math.Pow(2, float64(n-1))))
    fmt.Println(uint64(1) << uint(n-1))
}
Run Code Online (Sandbox Code Playgroud)

输出表明这两种方法是不同的:

9223372036854775808
0
Run Code Online (Sandbox Code Playgroud)


icz*_*cza 6

math.Pow()被实现来对float64数字进行操作。用于计算 2 的幂的位移只能应用于整数,并且只能应用于结果适合int64(或uint64) 的微小子集。

如果您有这种特殊情况,我们非常欢迎您使用位移位。

任何其他结果大于math.MaxInt64或基数不是2(或 的幂2)的情况都需要浮点运算。

另请注意,即使实现对上述可能的微小子集的检测,结果也是2 的补码格式,也必须转换为IEEE 754格式,因为 的返回值math.Pow()float64(尽管这些数字可以被缓存),您很可能再次将其转换回int64.

再次强调:如果您需要性能,请使用显式位移位。