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

When creating an array in Go, it seems the array will always be zeroed, even if different values will be set right after the initialization, for example when the value should be set to the index in the array.

One way to avoid this is to use array literals, such as a = [5]int{0,1,2,3,4}, but it becomes impractical for long arrays. I'm wondering what is the best way to perform the initialization.

Surprisingly, the named return function outperforms the composite literal initialization for large arrays.

I've created the following benchmark to compare the performance:

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
}

Results:

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

Observations:

  1. I did not expect that naming the return value would make a difference for the loop, I thought surely the compiler would do some optimization. For 1,000,000, it becomes faster than the literal initialization.

  2. I expected a linear scaling, I do not understand why it is not the case, for either of the methods.

I'm not sure how to explain this, even though it seems to be extremely basic. Any ideas ?

Edit: There is an open issue on Github complaining that naming the return value should not make a difference. I also found this to be a surprising behavior.

du9698
du9698 好点,谢谢。
大约一年之前 回复
douci1615
douci1615 这也是我所怀疑的,我认为对性能的影响很小。但是,如何解释通过带有命名的return的函数内部的循环初始化更快呢?
大约一年之前 回复
drkxgs9358
drkxgs9358 归零实际上是一个golang功能(“零值”),出于安全原因进行;请参阅golang.org/ref/spec#The_zero_value
大约一年之前 回复
dqsk4643
dqsk4643 如果您想知道三个版本为何不同的原因:查看汇编代码。就是这么说,没什么可期待的。还要注意,数组(而不是切片)在Go中并不常见,因此您真的不应该期望对数组(尤其是疯狂的大型数组)进行极其聪明的优化。不要指望壁钟和现代硬件上的输入大小之间的关系过于简单。现代硬件对于普通事物和正常事物的运转速度很快,但在其他事物上的运转速度可能较慢。
大约一年之前 回复
douxing7101
douxing7101 当变量不存在时(初始化之前),我不了解清零。
大约一年之前 回复
dongwuli5105
dongwuli5105 唯一的方法是在初始化时将所有值分配给数组。这可以是数组文字,也可以是函数的返回值或其他数组。但是,立即数是避免必须预先分配零值数组的唯一方法。
大约一年之前 回复

2个回答

The reason why your results are not linear with the array size is because not all operations involved in obtaining a new filled array are linear with the array size. For example you need memory allocation, optionally zeroing the allocated memory, a loop to fill the array, and you have to return (copy) the array's memory. Allocation is a good example which should not be linear with the size, also, copying memory should also not be linear (should increase, but not linearly).

One way to avoid a lengthy composite literal and asking a new array value that needs to be zeroed which afterwards gets filled is to have the value ready, and just assign it to the array variable.

What I mean by this is have a package-level variable store the computed / filled array (simplest filled with a simple loop), and when you need a new array filled the same, just assign the stored value:

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

If you add this to your benchmarks:

func BenchmarkArrayAssign(b *testing.B) {
    var r [N]int
    for n := 0; n < b.N; n++ {
        r = cache
    }
    result = r
}

Or simply:

func BenchmarkArrayAssign(b *testing.B) {
    for n := 0; n < b.N; n++ {
        result = cache
    }
}

This will outperform your fastest-so-far ArrayLoopNamedReturn by twice (when N = 1_000_000).

BenchmarkArrayAssign-4                  1000       1104829 ns/op
BenchmarkArrayLoop-4                     500       3822005 ns/op
BenchmarkArrayLoopNamedReturn-4          500       2326498 ns/op
du512053619
du512053619 我没想到即使分配内存也不是线性的,但是我不知道它是如何完成的。 复制应该是线性的。 在一般情况下,缓存分配不是很实用,但是作为基准测试是很好的,因为它应该是最佳的。
大约一年之前 回复



您可以按照您所说的使用文字初始化数组,否则该数组将具有默认的零值。 如果您能够创建一个数组并稍后设置其内容,那么这两个时刻之间的任何读取访问都将是不确定的(例如在C中)。</ p>

我同意使用 虽然数组字面量可用于大量元素,但这是内存安全的代价:)</ p>
</ div>

展开原文

原文

You either initialize the array with literals, as you said, or the array will have default, zero values. If you were able to create an array and set its content later, any read access between those two moments would be undefined (like in C).

I agree that it's not practical to use an array literal for a large number of elements though, but that's the price of memory safety :)

Csdn user default icon
上传中...
上传图片
插入图片
抄袭、复制答案,以达到刷声望分或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号。是时候展现真正的技术了!
立即提问
相关内容推荐