英文:
Why is math.Pow performance worse than bitshifting?
问题
在解决Exercism网站上的这个练习时,我使用了标准的math.Pow包函数来获取2的幂次方。
return uint64(math.Pow(2, float64(n-1)))
在查看社区解决方案后,我发现可以使用位移操作来实现相同的功能:
return uint64(1 << uint(n-1)), nil
令我惊讶的是,这两种方法之间存在很大的性能差异:
位移操作
幂运算
我原以为Go编译器会意识到math.Pow使用的是常数2作为底数,并自动进行位移操作,而不需要我显式地进行位移操作。我唯一能看到的其他区别是float64的转换,以及math.Pow是在浮点数上操作,而不是整数。
为什么编译器不对幂运算进行优化,以实现与位移操作类似的性能?
英文:
When solving this exercise on Exercism website, I have used the standard math.Pow package function to get the raising powers of two.
return uint64(math.Pow(2, float64(n-1)))
After checking the community solutions, I found a solution using bit shifting to achieve the same thing:
return uint64(1 << uint(n-1)), nil
What surprised me is that there is a big performance difference between the two:
bit-shifting
math-pow
I thought that the Go compiler would recognize that math.Pow uses a constant 2 as the base and just utilize bit shifting on its own, without me explicitly doing it so. The only other difference I can see is the conversion of the float64 and that math.Pow is operating on floats and not on integers.
Why doesn't the compiler optimize the power operation to achieve performance similar to bit shifting?
答案1
得分: 7
首先,请注意uint64(1) << (n-1)
是你问题中出现的表达式uint64(1 << uint(n-1))
的更好版本。表达式1<<n
是一个int
,因此有效的移位值在0和30或62之间,具体取决于int的大小。uint64(1) << n
允许n
在0和63之间。
总的来说,你提出的优化是不正确的。编译器必须能够推断出n
在特定范围内。
请参考这个示例(在playground上):
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))
}
输出结果表明这两种方法是不同的:
9223372036854775808
0
英文:
First, note that uint64(1) << (n-1)
is a better version of the expression uint64(1 << uint(n-1))
that appears in your question. The expression 1<<n
is an int
, so valid shift values are between 0 and either 30 or 62 depending on the size of int. uint64(1) << n
allows n
between 0 and 63.
In general, the optimization you suggest is incorrect. The compiler would have to be able to deduce that n
is within a particular range.
See this example (on playground)
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))
}
The output demonstrates that the two methods are different:
9223372036854775808
0
答案2
得分: 6
math.Pow()
函数是用于操作float64
类型的数字。位移运算只能应用于整数,并且只能在结果适合int64
(或uint64
)的极小子集上进行。
如果你有这样的特殊情况,可以使用位移运算。
对于其他任何结果大于math.MaxInt64
或基数不是2
(或2
的幂)的情况,都需要使用浮点数运算。
还要注意,即使实现了上述可能的极小子集的检测,结果也是以二进制补码格式表示的,因为math.Pow()
的返回值是float64
(尽管这些数字可以被缓存),所以还需要将其转换为IEEE 754格式,而你很可能会再次将其转换回int64
。
再次强调:如果你需要性能,可以使用显式的位移运算。
英文:
math.Pow()
is implemented to operate on float64
numbers. Bit shifting to calculate powers of 2 can only be applied on integers, and only on a tiny subset where the result fits into int64
(or uint64
).
If you have such special case, you're more than welcome to use bit shifting.
Any other case where the result is bigger than math.MaxInt64
or where the base is not 2
(or a power of 2
) requires floating point arithmetic.
Also note that even if detection of the above possible tiny subset would be implemented, the result is in 2's complement format, which also would have to be converted to IEEE 754 format because the return value of math.Pow()
is float64
(although these numbers could be cached), which again you'd most likely convert back to int64
.
Again: if you need performance, use explicit bit shifting.
答案3
得分: 3
因为从未实现过这样的优化。
Go编译器旨在具有快速的编译时间。因此,一些优化被认为不值得。这样做可以节省编译时间,但会牺牲一些运行时性能。
英文:
Because such optimization was never implemented.
The go compiler aims to have fast compile time. Thus, some optimizations are decided to be not worth it. This saves compilation time at the expense of some run-time.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论