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条)

报告相同问题?

悬赏问题

  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)
  • ¥15 电力市场出清matlab yalmip kkt 双层优化问题
  • ¥30 ros小车路径规划实现不了,如何解决?(操作系统-ubuntu)