我是围棋新手,我正在使用 Go Tour 学习围棋。挑战之一是求一个数的平方根。给出了一个公式,初始值为 1。
摆弄了很长一段时间后,我做了一些数学计算,找到了一个更好的初始值(平均迭代次数减少了约 25-35%,平均误差减少了约 7-10%)。这个首字母看起来数字越大越好。
接下来我想做的是检查我的函数计算 1 到 100,000 的平方根需要多长时间。只是为了好玩。
查看 Go 的 time 包,我使用
time.Now()
和 time.Since()
函数来计算函数的总时间和 math.Sqrt()
总时间。但奇怪的是,这些数字非常非常低(我的函数约为 3 毫秒,0 math.Sqrt()
)。
我不确定我的计时代码是否不正确,如果我的函数实际上只需要〜3毫秒(不包括打印输出),为什么
math.Sqrt()
需要0时间? (它是否可能已经计算出来并且只是在查找?),也许正在发生某种线程操作?
我不知道。
这是我所有的代码
package main
import (
"fmt"
"math"
"strconv"
"time"
)
func Sqrt(x float64) (float64, float64) {
count := 0
initial := 1
// initial = 1<<2^(digit count)
for n := x;n > 1; {
n = n / 10
initial = initial<<2
count++
}
z := x / float64(initial)
var p float64
i := 0.0
for ; math.Abs(z-p) > 0.0000000001; i = i + 1 {
p = z
z -= (z*z - x) / (2 * z)
}
return z, i
}
func compute(x int) (float64, float64) {
input := float64(x)
guess, iterations := Sqrt(input)
expected := math.Sqrt(input)
err := float64(math.Abs(guess-expected))
fmt.Printf("Sqrt: %v\nExpected: %v\nGuess: %v\nError: %v\nIterations: %v\n\n", input, expected, guess, err, iterations)
return iterations, err
}
func main() {
size := 100000
var totalIterations []int
var totalErrs []string
var iterSum float64
var errSum float64
for i := 1; i < size + 1; i++ {
iter, err := compute(i)
totalIterations = append(totalIterations, int(iter))
formatedErr := strconv.FormatFloat(err, 'E', -1, 64)
totalErrs = append(totalErrs, formatedErr)
iterSum += iter
errSum += err
}
fmt.Printf("Iterations sum %v\nAverage iteration count: %v\nError sum: %v\nError average: %v\n", iterSum, iterSum/float64(len(totalIterations)), errSum, errSum/float64(len(totalErrs)))
// re-running the loops for finding time (temporary), I will incoperate it to the above loop later
customTimeStart := time.Now()
for i := 1; i < size + 1; i++ {
Sqrt(float64(i))
}
elapsedCustom := time.Since(customTimeStart)
mathTimeStart := time.Now()
for i := 1; i < size + 1; i++ {
math.Sqrt(float64(i))
}
elapsedMath := time.Since(mathTimeStart)
fmt.Printf("Total custom time: %s\nTotal math time: %s\n", elapsedCustom, elapsedMath)
}
Golang 编译器内联
math.Sqrt
函数并调用内部函数 math.sqrt
。
然后在大多数硬件平台上,编译器将
math.sqrt
替换为 SQRT 操作码。在AMD64平台上它是SQRTSD
汇编指令。请参阅此处了解更多详情。
难怪调用
math.Sqrt
需要纳秒。
自己尝试一下。将你的程序编译成汇编
go build -gcflags=-S some.go
然后检查组件的
compute
功能:
0x001a 00026 (/mnt/d/tmp/try-go/trygo/some.go:33) XORPS X0, X0
0x001d 00029 (/mnt/d/tmp/try-go/trygo/some.go:33) CVTSQ2SD AX, X0
0x0022 00034 (/mnt/d/tmp/try-go/trygo/some.go:33) MOVSD X0, main.input+72(SP)
0x0028 00040 (/mnt/d/tmp/try-go/trygo/some.go:34) PCDATA $1, $0
0x0028 00040 (/mnt/d/tmp/try-go/trygo/some.go:34) CALL main.Sqrt(SB)
0x002d 00045 (/mnt/d/tmp/try-go/trygo/some.go:34) MOVSD X1, main.iterations+64(SP)
0x0033 00051 (/usr/local/go/src/math/sqrt.go:94) MOVSD main.input+72(SP), X2
0x0039 00057 (/usr/local/go/src/math/sqrt.go:94) SQRTSD X2, X3
0x003d 00061 (/mnt/d/tmp/try-go/trygo/some.go:36) MOVUPS X0, X4
0x0040 00064 (/mnt/d/tmp/try-go/trygo/some.go:36) SUBSD X3, X0
看到这行
0x0039 00057 (/usr/local/go/src/math/sqrt.go:94) SQRTSD X2, X3
了吗?这是 Go 编译器为 math.Sqrt
调用生成的内容。