dongpang2029 2019-08-07 09:32
浏览 204
已采纳

如何在不将所有值归零的情况下初始化长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.

  • 写回答

2条回答 默认 最新

  • donglipi4495 2019-08-07 13:14
    关注

    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
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 matlab yalmip kkt 双层优化问题
  • ¥15 如何在3D高斯飞溅的渲染的场景中获得一个可控的旋转物体
  • ¥88 实在没有想法,需要个思路
  • ¥15 MATLAB报错输入参数太多
  • ¥15 python中合并修改日期相同的CSV文件并按照修改日期的名字命名文件
  • ¥15 有赏,i卡绘世画不出
  • ¥15 如何用stata画出文献中常见的安慰剂检验图
  • ¥15 c语言链表结构体数据插入
  • ¥40 使用MATLAB解答线性代数问题
  • ¥15 COCOS的问题COCOS的问题