为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
12345678_0001 2023-07-10 16:28:58
在 Go 中创建数组时,即使在初始化后立即设置不同的值,数组似乎也总是会被清零,例如当该值应设置为数组中的索引时。避免这种情况的一种方法是使用数组文字,例如a = [5]int{0,1,2,3,4},但对于长数组来说它变得不切实际。我想知道执行初始化的最佳方法是什么。令人惊讶的是,对于大型数组,命名返回函数的性能优于复合文字初始化。我创建了以下基准来比较性能:package mainimport "testing"const N = 1000000var result [N]intfunc 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,000BenchmarkArrayLoop-8                      200000              9041 ns/opBenchmarkArrayLoopNamedReturn-8           200000              6327 ns/opBenchmarkArrayLiteral-8                   300000              4300 ns/opN = 100,000BenchmarkArrayLoop-8                       10000            191582 ns/opBenchmarkArrayLoopNamedReturn-8            20000             76125 ns/opBenchmarkArrayLiteral-8                    20000             62714 ns/opN = 1,000,000BenchmarkArrayLoop-8                         500           2635713 ns/opBenchmarkArrayLoopNamedReturn-8             1000           1537282 ns/opBenchmarkArrayLiteral-8                     1000           1854348 ns/op观察结果:我没想到命名返回值会对循环产生影响,我认为编译器肯定会做一些优化。对于 1,000,000,它变得比文字初始化更快。我期望线性缩放,但我不明白为什么这两种方法都不是这种情况。我不知道如何解释这一点,尽管它似乎非常基本。有任何想法吗 ?
查看完整描述

2 回答

?
梵蒂冈之花

TA贡献1900条经验 获得超5个赞

结果与数组大小非线性的原因是因为并非获取新填充数组所涉及的所有操作都与数组大小线性。例如,您需要内存分配,可以选择将分配的内存清零,循环填充数组,并且必须返回(复制)数组的内存。分配是一个很好的例子,它不应该与大小成线性关系,而且,复制内存也不应该是线性的(应该增加,但不是线性的)。


避免使用冗长的复合文字并询问需要清零并随后填充的新数组值的一种方法是准备好该值,然后将其分配给数组变量。


我的意思是有一个包级变量存储计算/填充的数组(最简单的填充是一个简单的循环),当您需要一个新的数组填充相同的数组时,只需分配存储的值:


var cache [N]int


func init() {

    for i := range cache {

        cache[i] = i

    }

}


// If you now need a new array:

var result = cache

// Or re-init an existing array:

result = cache

如果您将其添加到您的基准中:


func BenchmarkArrayAssign(b *testing.B) {

    var r [N]int

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

        r = cache

    }

    result = r

}

或者简单地:


func BenchmarkArrayAssign(b *testing.B) {

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

        result = cache

    }

}

这将比您迄今为止最快的速度快ArrayLoopNamedReturn 两倍(当 时N = 1_000_000)。


BenchmarkArrayAssign-4                  1000       1104829 ns/op

BenchmarkArrayLoop-4                     500       3822005 ns/op

BenchmarkArrayLoopNamedReturn-4          500       2326498 ns/op


查看完整回答
反对 回复 2023-07-10
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

正如您所说,您可以使用文字初始化数组,或者数组将具有默认的零值。如果您能够创建一个数组并稍后设置其内容,那么这两个时刻之间的任何读取访问都将是未定义的(就像在 C 中一样)。

我同意对大量元素使用数组文字是不切实际的,但这就是内存安全的代价:)


查看完整回答
反对 回复 2023-07-10
  • 2 回答
  • 0 关注
  • 81 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信