英文:
How to convert a slice of one numeric type to another type
问题
我正在尝试使用Go语言,并且对它还很陌生。我已经成功完成了教程,并且现在正在编写一个小程序来评估它在我通常进行的操作类型上的性能。我有一个很长的float32类型的切片,需要将其转换为尽可能高效的float64类型的切片。除了通过迭代切片的元素并显式地转换每个元素的类型(例如output[i] = float64(data[i])),是否有其他方法可以在不需要迭代的情况下转换整个切片?我尝试搜索解决方案,但没有找到直接相关的内容。
英文:
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.
答案1
得分: 5
Go是一种相当底层的语言,这意味着通过切片进行迭代是最高效的方法。其他语言可能有内置函数来处理这种情况,但它们只是通过切片进行迭代,没有办法避免迭代。但是有一些技巧,特别是使用range并避免对切片进行索引,因为在越界检查中会有开销。这将是最高效的方法:
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)
请注意,在range循环中使用:=
会效率低下,因为在当前版本的Go中,变量每次都会被丢弃并重新创建,而不是被重用。使用range
而不是for i=0; i<n; i++
更高效,因为它可以节省对ar
进行边界检查的开销。
英文:
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
.
答案2
得分: 2
对于性能的声明,始终保持怀疑是明智的。例如,“在范围循环中使用:=可能效率低下,因为...变量每次都被丢弃和重新创建,而不是被重用。”
让我们看一下连续三次运行的基准测试结果。
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() {}
输出:
$ 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
$
根据已删除的评论的要求,这里是一个仅使用系统时间的基准测试。
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()
}
输出:
$ 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
$
英文:
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:
<pre>
$ 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
$
</pre>
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:
<pre>
$ 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
$
</pre>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论