I'm experimenting with the Go language and am quite new to it. I have successfully gone through the tutorials and am now writing a little program to evaluate its performance for the type of operations that I typically do. I have a lengthy slice of float32 type and need to convert it to a slice of type float64 as efficiently as possible. Other than iterating through the elements of the slice and explicitly converting types of individual elements via output[i] = float64(data[i]), is there method that I can use to convert the entire slice without need for iteration? I've tried searching for a solution but have not found anything directly related.
-
3No... even if this were a builtin, it could hardly do anything other than iterate over the elements in the same way that your 4 lines of code would do. – Paul Hankin Nov 16 '14 at 14:18
2 Answers
Go is quite low-level, this means that iterating through the slice is the most efficient method. Other languages may have built-in functions for such things, but all they do is iterate through the slice, there is no way to do it without the iteration. But there are some tricks, specifically use range and avoid indexing the slice as there is overhead in the out of bounds check. This would be the most efficient:
func convertTo64(ar []float32) []float64 {
newar := make([]float64, len(ar))
var v float32
var i int
for i, v = range ar {
newar[i] = float64(v)
}
return newar
}
slice32 := make([]float32, 1000)
slice64 := convertTo64(slice32)
Note that the use of :=
in the range loop would be inefficient because in the current version of Go the variable is thrown away and recreated each time instead of being reused. Using range
instead of for i=0; i<n; i++
is more efficient because it saves bounds checks on ar
.

- 13,348
- 18
- 82
- 138
-
This is really very helpful. Thank you kindly for sharing your expertise. – user1503949 Nov 16 '14 at 17:45
-
I've just been playing around with it for the last few days but it does certainly seem to be nice and easy to pick up. – user1503949 Nov 16 '14 at 17:48
-
1@Alasdair: Your inefficiency claim about ":= in the range loop" doesn't make sense for stack or register variables. – peterSO Nov 16 '14 at 19:21
-
I discovered this through benchmarking and reported it on golang-devs. – Alasdair Nov 17 '14 at 04:50
It's always wise to be skeptical of performance claims. For example, "the use of := in the range loop would be inefficient because ... the variable is thrown away and recreated each time instead of being reused."
Let's look at benchmark results from three successive runs.
floats_test.go
:
package main
import "testing"
var slice64 []float64
func FuncVar(f32 []float32) []float64 {
f64 := make([]float64, len(f32))
var f float32
var i int
for i, f = range f32 {
f64[i] = float64(f)
}
return f64
}
func BenchmarkFuncVar(b *testing.B) {
f32 := make([]float32, 1024)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
slice64 = FuncVar(f32)
}
}
func RangeVar(f32 []float32) []float64 {
f64 := make([]float64, len(f32))
for i, f := range f32 {
f64[i] = float64(f)
}
return f64
}
func BenchmarkRangeVar(b *testing.B) {
f32 := make([]float32, 1024)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
slice64 = RangeVar(f32)
}
}
func main() {}
Output:
$ go test -v -run=! -bench=. testing: warning: no tests to run PASS BenchmarkFuncVar 100000 12260 ns/op 8192 B/op 1 allocs/op BenchmarkRangeVar 100000 12125 ns/op 8192 B/op 1 allocs/op ok so/test 2.703s $ go test -v -run=! -bench=. testing: warning: no tests to run PASS BenchmarkFuncVar 100000 12620 ns/op 8192 B/op 1 allocs/op BenchmarkRangeVar 100000 12623 ns/op 8192 B/op 1 allocs/op ok so/test 2.782s $ go test -v -run=! -bench=. testing: warning: no tests to run PASS BenchmarkFuncVar 100000 12730 ns/op 8192 B/op 1 allocs/op BenchmarkRangeVar 100000 12971 ns/op 8192 B/op 1 allocs/op ok so/test 2.852s $
As requested by a comment that has been deleted. Here's a benchmark using just the system time.
package main
import (
"fmt"
"time"
)
const N = 1e6
var f32 = make([]float32, 1024)
var slice64 []float64
func FuncVar(f32 []float32) []float64 {
f64 := make([]float64, len(f32))
var f float32
var i int
for i, f = range f32 {
f64[i] = float64(f)
}
return f64
}
func BenchmarkFuncVar() {
t1 := time.Now()
for i := 0; i < N; i++ {
slice64 = FuncVar(f32)
}
t2 := time.Now()
fmt.Println("FuncVar", t2.Sub(t1))
}
func RangeVar(f32 []float32) []float64 {
f64 := make([]float64, len(f32))
for i, f := range f32 {
f64[i] = float64(f)
}
return f64
}
func BenchmarkRangeVar() {
t1 := time.Now()
for i := 0; i < N; i++ {
slice64 = RangeVar(f32)
}
t2 := time.Now()
fmt.Println("RangeVar", t2.Sub(t1))
}
func main() {
BenchmarkFuncVar()
BenchmarkRangeVar()
}
Output:
$ go build floata.go && ./floata FuncVar 10.479653966s RangeVar 10.208178244s $ go build floata.go && ./floata FuncVar 10.123357283s RangeVar 10.173007394s $ go build floata.go && ./floata FuncVar 9.935580721s RangeVar 10.109644784s $ go build floata.go && ./floata FuncVar 10.070552761s RangeVar 10.317730473s $ go build floata.go && ./floata FuncVar 10.075578601s RangeVar 10.012273678s $

- 158,998
- 31
- 281
- 276
-
1Hi peterSo, I had discovered the := range issue in a benchmark I did a few weeks ago and reported it on Golang-Devs. Redoing that benchmark now I can see that it gives no difference between the two. Most likely by earlier benchmark was flawed. Thanks for taking the take to double check. – Alasdair Nov 17 '14 at 05:03