Golang中最快的数组清零方法

Golang中最快的数组清零方法 将数组所有元素设置为'0'的最快方法是什么?

我是这样做的。有没有更快的方法?

for i := range seg { seg[i] = 0 } 
13 回复

是的!!!

更多关于Golang中最快的数组清零方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我认为从我发布的代码中可以非常清楚地看出我的意图。 有没有比我展示的方法更快的方式来用零填充那个切片?

最快清空数组的方法

你询问了清空数组的方法。因此,你得到了一个关于清空数组方法的回答。

然而,你似乎有些困惑。在 Go 语言中,数组和切片是两种不同的类型。

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]
}

Go Playground - The Go Programming Language

[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方法,但差异通常很小。

回到顶部