Golang中最快的数组清零方法
Golang中最快的数组清零方法
将数组所有元素设置为'0'的最快方法是什么?
我是这样做的。有没有更快的方法?
for i := range seg { seg[i] = 0 }
我认为从我发布的代码中可以非常清楚地看出我的意图。 有没有比我展示的方法更快的方式来用零填充那个切片?
最快清空数组的方法
你询问了清空数组的方法。因此,你得到了一个关于清空数组方法的回答。
然而,你似乎有些困惑。在 Go 语言中,数组和切片是两种不同的类型。
数组类型
切片类型
关于切片的问题,请发布一个新问题。请具体说明,并包含一个完整、可复现的示例。
Rust 的 fill 函数很可能使用了 C 语言的 memcpy 函数或其等效的汇编实现,这是最高效的方式。
我推测 Go 编译器会将以下模式优化为使用等效的 memcpy。这是一种非常常见的模式,由于其优化过程简单明了,因此肯定会被优化。
a := make([]int64, 1024*1024)
for i := range a {
a[i] = 0
}
您可以尝试重新定义数组中的值。例如:
var a [2]int
// 因为0是int类型的默认值
fmt.Println(a) // [0 0]
// 让我们给数组赋值一些值
a[0] = 10
a[1] = 20
fmt.Println(a) // [10 20]
// 重新定义数组
a = [2]int{0, 0}
fmt.Println(a) // [0 0]
这在我的情况下行不通,因为编译器提示 seg 不是常量数组。
go-projects go build twinprimes_ssoz.go
# command-line-arguments
./twinprimes_ssoz.go:232:27: non-constant array bound len(seg)
➜ go-projects
它是这样创建的,并在循环结束时在其内部被清零。
seg := make([]uint64, ((kb - 1) >> s) + 1)
geosoft1: 这是你要找的吗?
Go Playground - The Go Programming Language
s := [5]int{1,2,3,4,5} s=[5]int{}
不是。这个答案其他人已经以更通用的形式发布过了。
petrus:
a = [len(a)]int{}
也许这能帮到你
package main
import "fmt"
func main() {
arr := []int{1, 2, 3, 4, 5, 7, 8, 9, 10}
fmt.Println(arr)
value := 0
arrSize := len(arr)
fillArray(arr, arrSize, value)
fmt.Println(arr)
}
func fillArray(arr []int, arrSize, value int) {
arr[0] = value
for i := 1; i < arrSize; i *= 2 {
copy(arr[i:], arr[:i])
}
}
https://play.golang.org/p/wNjQrzJUpSQ
这是我所知道的填充数组最高效的方法。我认为它的时间复杂度是 O(log(n))。
var a [2]int
a = [2]int{0, 0}
你的例子不太实用。让我们把数组的大小稍微增加到 1024*1024。你打完那一百多万个零的列表了吗?
一个更实用、更通用的例子是:
var a [1024*1024]int
a = [len(a)]int{}
那么,你的例子就变成了:
package main
import "fmt"
func main() {
var a [2]int
// 因为 0 是 int 的默认值
fmt.Println(a) // [0 0]
// 让我们给数组赋一些值
a[0] = 10
a[1] = 20
fmt.Println(a) // [10 20]
// 重新定义数组
a = [len(a)]int{}
fmt.Println(a) // [0 0]
}
[0 0]
[10 20]
[0 0]
我认为这是一个棘手的问题。 在不同的机器上使用不同的数组大小执行以下脚本,你会得到不同的结果 🙂
package main
import (
"fmt"
"math"
"runtime"
"sync"
"time"
)
func main() {
var seg [1024 * 1024]int
// Part 1
fillArrayWithOnes(seg[:])
runtime.Gosched()
start := time.Now()
initializeSeq(seg[:])
elapsed := time.Now().Sub(start)
fmt.Printf("Sequential took %v\n", elapsed)
fmt.Println("ns:", int64(elapsed/time.Nanosecond))
// Part 2
fillArrayWithOnes(seg[:])
runtime.Gosched()
start = time.Now()
cleanWg := new(sync.WaitGroup)
numGoroutines := runtime.NumCPU()
sliceSize := int(math.Ceil(float64(len(seg)) / float64(numGoroutines)))
for i := 0; i < numGoroutines; i++ {
start := i * sliceSize
end := (i + 1) * sliceSize
if end > len(seg) {
end = len(seg)
}
partialKeys := seg[start:end]
cleanWg.Add(1)
go initialize(partialKeys, cleanWg)
}
cleanWg.Wait()
elapsed = time.Now().Sub(start)
fmt.Printf("Goroutines took %v\n", elapsed)
fmt.Println("ns:", int64(elapsed/time.Nanosecond))
}
func fillArrayWithOnes(currentArray []int) {
for i := range currentArray {
currentArray[i] = 1
}
}
func initialize(currentArray []int, wg *sync.WaitGroup) {
defer wg.Done()
for i := range currentArray {
currentArray[i] = 0
}
}
func initializeSeq(currentArray []int) {
for i := range currentArray {
currentArray[i] = 0
}
}
嘿,谢谢 @guille-acosta。
我修改了你的方法,使其适用于 unit64 类型以及我的数组类型,并简化了它。
func fillArray(arr []uint64, value uint64) {
arr[0] = value
for i := 1; i < len(arr); i *= 2 {
copy(arr[i:], arr[:i])
}
}
然而,在我的代码中进行测试后,它并没有带来任何可测量的性能提升。
顺便提一下,我在2020年3月在Rust论坛上问过这个问题,今天碰巧又问了一次。他们实际上现在已经在他们的标准库中包含了一个 fill 方法,并且正在寻求优化它。
我猜想,要使你的技术真正更快,可能也需要在汇编层面实现。此外,Rust的方法适用于各种数字类型,所以这也是需要考虑的一点,以便使其成为适用于所有数组数字类型的通用函数。
我真的认为添加一个优化的 fill 方法对Go程序员也会非常有益,仅从代码更易读的美学好处来看就是如此。
以下是今天在Rust论坛上的讨论。
The Rust Programming Language Forum – 17 Mar 21
Fastest way to zero an array, followup
再次感谢。 🙂
在Go语言中,数组清零最快的方法是使用内置的copy函数配合一个已清零的数组,或者直接使用for循环。对于数组,for循环通常是最直接且高效的方式。以下是几种方法的比较:
1. 使用for循环(你当前的方法)
for i := range seg {
seg[i] = 0
}
这是最常用且性能良好的方法,编译器会优化为高效的底层内存操作。
2. 使用copy函数
zero := [len(seg)]int{} // 已初始化为0的数组
copy(seg[:], zero[:])
copy函数在底层使用内存复制,对于大数组可能更快,但需要额外创建零数组。
3. 使用for循环的索引版本
for i := 0; i < len(seg); i++ {
seg[i] = 0
}
性能与range版本相当,但range更简洁。
性能对比示例
以下是一个简单的基准测试,展示不同方法的性能:
package main
import (
"testing"
)
var arr [1000]int
func BenchmarkForRange(b *testing.B) {
for n := 0; n < b.N; n++ {
for i := range arr {
arr[i] = 0
}
}
}
func BenchmarkForIndex(b *testing.B) {
for n := 0; n < b.N; n++ {
for i := 0; i < len(arr); i++ {
arr[i] = 0
}
}
}
func BenchmarkCopy(b *testing.B) {
zero := [len(arr)]int{}
for n := 0; n < b.N; n++ {
copy(arr[:], zero[:])
}
}
运行基准测试:
go test -bench=.
通常,for循环(无论是range还是索引版本)与copy函数性能相近,但for循环更通用。对于数组清零,你当前的方法已经是最快的之一。如果数组非常大(例如百万元素级别),可以尝试copy方法,但差异通常很小。


