I'm new to go and I was learning Go using the Go Tour. One of the challenges was to find a square root of a number. A formula was given with an initial value of 1.
After fiddling with it for a long while, I did some math to find a somewhat better initial value (~25-35% less average iterations, ~7-10% less average error). This initial seems better with bigger numbers.
Next thing I wanted to do was to check how long it takes for my function to compute square root of 1 to 100,000. Just for fun.
Looking at Go's time package, I made use of the time.Now() and time.Since() functions to calculate both my function's total time and math.Sqrt() total time. But the numbers are oddly very, very low (~3ms for my function, 0 math.Sqrt()).
I'm not sure if my timing code is incorrect, if my function actually only takes ~3ms (excluding printouts), and why math.Sqrt() takes 0 time? (Is it perhaps already computed and it is just looking it up?), perhaps somne kind of threading action is happening?
I have no idea.
Here is all my code
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)
}
The Golang compiler optimizes
math.Sqrtfunction by replacing it with a call to the internal functionmath.sqrt. Subsequently, on most hardware platforms, including the AMD64 architecture, the compiler further substitutesmath.sqrtwith platform-dependentSQRTopcode. For AMD64, it isSQRTSDassembly instruction (see here ).As a result, calls to math.Sqrt exhibit extremely fast execution times, measured in fractions of nanoseconds. You can verify these optimizations by compiling your program into assembly using the command
go build -gcflags=-S some.go.Then check the assembly for
computefunction:See this line
0x0039 00057 (/usr/local/go/src/math/sqrt.go:94) SQRTSD X2, X3? This is what Go compiler generated formath.Sqrtcall.Benchmarking
Go has a better way to evaluate performance. It is called benchmarking
Run it:
go test -benchmem -bench ^Benchmark.*$ .Result: