如何在不将所有值清零的情况下初始化一个长Golang数组?

在 Go 中创建数组时,即使在初始化后立即设置不同的值,数组似乎也总是会被清零,例如当该值应设置为数组中的索引时。

避免这种情况的一种方法是使用数组文字,例如a = [5]int{0,1,2,3,4},但对于长数组来说它变得不切实际。我想知道执行初始化的最佳方法是什么。

令人惊讶的是,对于大型数组,命名返回函数的性能优于复合文字初始化。

我创建了以下基准来比较性能:


package main


import "testing"


const N = 1000000


var result [N]int


func arrayLiteral() [N]int {

    // Replace the 3 dots with the actual value

    // I copy-pasted the output of an other program to do this

    return [N]int{0,1,2,3,...,N-1}

}


func arrayLoopNamedReturn() (a [N]int) {

    for i := 0; i < N; i++ {

        a[i] = i

    }

    return

}


func arrayLoop() [N]int {

    var a [N]int

    for i := 0; i < N; i++ {

        a[i] = i

    }

    return a

}


func BenchmarkArrayLoop(b *testing.B) {

    var r [N]int

    for n := 0; n < b.N; n++ {

        r = arrayLoop()

    }

    result = r

}


func BenchmarkArrayLoopNamedReturn(b *testing.B) {

    var r [N]int

    for n := 0; n < b.N; n++ {

        r = arrayLoopNamedReturn()

    }

    result = r

}


func BenchmarkArrayLiteral(b *testing.B) {

    var r [N]int

    for n := 0; n < b.N; n++ {

        r = arrayLiteral()

    }

    result = r

}

结果:


N = 10,000

BenchmarkArrayLoop-8                      200000              9041 ns/op

BenchmarkArrayLoopNamedReturn-8           200000              6327 ns/op

BenchmarkArrayLiteral-8                   300000              4300 ns/op


N = 100,000

BenchmarkArrayLoop-8                       10000            191582 ns/op

BenchmarkArrayLoopNamedReturn-8            20000             76125 ns/op

BenchmarkArrayLiteral-8                    20000             62714 ns/op


N = 1,000,000

BenchmarkArrayLoop-8                         500           2635713 ns/op

BenchmarkArrayLoopNamedReturn-8             1000           1537282 ns/op

BenchmarkArrayLiteral-8                     1000           1854348 ns/op

观察结果:

  1. 我没想到命名返回值会对循环产生影响,我认为编译器肯定会做一些优化。对于 1,000,000,它变得比文字初始化更快。

  2. 我期望线性缩放,但我不明白为什么这两种方法都不是这种情况。

我不知道如何解释这一点,尽管它似乎非常基本。有任何想法吗 ?


12345678_0001
浏览 85回答 2
2回答

梵蒂冈之花

结果与数组大小非线性的原因是因为并非获取新填充数组所涉及的所有操作都与数组大小线性。例如,您需要内存分配,可以选择将分配的内存清零,循环填充数组,并且必须返回(复制)数组的内存。分配是一个很好的例子,它不应该与大小成线性关系,而且,复制内存也不应该是线性的(应该增加,但不是线性的)。避免使用冗长的复合文字并询问需要清零并随后填充的新数组值的一种方法是准备好该值,然后将其分配给数组变量。我的意思是有一个包级变量存储计算/填充的数组(最简单的填充是一个简单的循环),当您需要一个新的数组填充相同的数组时,只需分配存储的值:var cache [N]intfunc init() {&nbsp; &nbsp; for i := range cache {&nbsp; &nbsp; &nbsp; &nbsp; cache[i] = i&nbsp; &nbsp; }}// If you now need a new array:var result = cache// Or re-init an existing array:result = cache如果您将其添加到您的基准中:func BenchmarkArrayAssign(b *testing.B) {&nbsp; &nbsp; var r [N]int&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; r = cache&nbsp; &nbsp; }&nbsp; &nbsp; result = r}或者简单地:func BenchmarkArrayAssign(b *testing.B) {&nbsp; &nbsp; for n := 0; n < b.N; n++ {&nbsp; &nbsp; &nbsp; &nbsp; result = cache&nbsp; &nbsp; }}这将比您迄今为止最快的速度快ArrayLoopNamedReturn 两倍(当 时N = 1_000_000)。BenchmarkArrayAssign-4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 1000&nbsp; &nbsp; &nbsp; &nbsp;1104829 ns/opBenchmarkArrayLoop-4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;500&nbsp; &nbsp; &nbsp; &nbsp;3822005 ns/opBenchmarkArrayLoopNamedReturn-4&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 500&nbsp; &nbsp; &nbsp; &nbsp;2326498 ns/op

慕莱坞森

正如您所说,您可以使用文字初始化数组,或者数组将具有默认的零值。如果您能够创建一个数组并稍后设置其内容,那么这两个时刻之间的任何读取访问都将是未定义的(就像在 C 中一样)。我同意对大量元素使用数组文字是不切实际的,但这就是内存安全的代价:)
打开App,查看更多内容
随时随地看视频慕课网APP