go make slice比[] {1,1,1,1}慢一点

时间:2017-11-30 08:38:46

标签: go

我正在开发一个程序,它分配了大量的[] int,长度为4,3,2
使用a:=[]{1,1,1}发现比a:=make([]int,3) a[0] = 1 a[1]=1 a[2]= 1

快一点

我的问题:为什么a:=[]{1,1,1}a:=make([]int,3) a[0] = 1 a[1]=1 a[2]= 1更快?

func BenchmarkMake(b *testing.B) {
    var array []int
    for i := 0; i < b.N; i++ {
        array = make([]int, 4)
        array[0] = 1
        array[1] = 1
        array[2] = 1
        array[3] = 1
    }
}

func BenchmarkDirect(b *testing.B) {
    var array []int
    for i := 0; i < b.N; i++ {
        array = []int{1, 1, 1, 1}
    }

    array[0] = 1
}

BenchmarkMake-4 50000000 34.3 ns / op
BenchmarkDirect-4 50000000 33.8 ns / op

1 个答案:

答案 0 :(得分:1)

让我们看一下以下代码的基准输出

package main

import "testing"

func BenchmarkMake(b *testing.B) {
    var array []int
    for i := 0; i < b.N; i++ {
        array = make([]int, 4)
        array[0] = 1
        array[1] = 1
        array[2] = 1
        array[3] = 1
    }
}

func BenchmarkDirect(b *testing.B) {
    var array []int
    for i := 0; i < b.N; i++ {
        array = []int{1, 1, 1, 1}
    }
    array[0] = 1
}

func BenchmarkArray(b *testing.B) {
    var array [4]int
    for i := 0; i < b.N; i++ {
        array = [4]int{1, 1, 1, 1}
    }
    array[0] = 1
}

通常输出看起来像那样

$ go test -bench . -benchmem -o alloc_test -cpuprofile cpu.prof
goos: linux
goarch: amd64
pkg: test
BenchmarkMake-8         30000000                61.3 ns/op            32 B/op          1 allocs/op
BenchmarkDirect-8       20000000                60.2 ns/op            32 B/op          1 allocs/op
BenchmarkArray-8        1000000000               2.56 ns/op            0 B/op          0 allocs/op
PASS
ok      test    6.003s

差异很小,在某些情况下可能相反。

让我们看一下分析数据

$go tool pprof -list 'Benchmark.*' cpu.prof 

ROUTINE ======================== test.BenchmarkMake in /home/grzesiek/go/src/test/alloc_test.go
     260ms      1.59s (flat, cum) 24.84% of Total
         .          .      5:func BenchmarkMake(b *testing.B) {
         .          .      6:   var array []int
      40ms       40ms      7:   for i := 0; i < b.N; i++ {
      50ms      1.38s      8:       array = make([]int, 4)
         .          .      9:       array[0] = 1
     130ms      130ms     10:       array[1] = 1
      20ms       20ms     11:       array[2] = 1
      20ms       20ms     12:       array[3] = 1
         .          .     13:   }
         .          .     14:}
ROUTINE ======================== test.BenchmarkDirect in /home/grzesiek/go/src/test/alloc_test.go
      90ms      1.66s (flat, cum) 25.94% of Total
         .          .     16:func BenchmarkDirect(b *testing.B) {
         .          .     17:   var array []int
      10ms       10ms     18:   for i := 0; i < b.N; i++ {
      80ms      1.65s     19:       array = []int{1, 1, 1, 1}
         .          .     20:   }
         .          .     21:   array[0] = 1
         .          .     22:}
ROUTINE ======================== test.BenchmarkArray in /home/grzesiek/go/src/test/alloc_test.go
     2.86s      2.86s (flat, cum) 44.69% of Total
         .          .     24:func BenchmarkArray(b *testing.B) {
         .          .     25:   var array [4]int
     500ms      500ms     26:   for i := 0; i < b.N; i++ {
     2.36s      2.36s     27:       array = [4]int{1, 1, 1, 1}
         .          .     28:   }
         .          .     29:   array[0] = 1
         .          .     30:}

我们可以看到作业需要一些时间。

了解我们为什么需要查看汇编程序代码。

$go tool pprof -disasm 'BenchmarkMake' cpu.prof

     .          .     4eda93: MOVQ AX, 0(SP)                             ;alloc_test.go:8
  30ms       30ms     4eda97: MOVQ $0x4, 0x8(SP)                         ;test.BenchmarkMake alloc_test.go:8
     .          .     4edaa0: MOVQ $0x4, 0x10(SP)                       ;alloc_test.go:8
  10ms      1.34s     4edaa9: CALL runtime.makeslice(SB)                 ;test.BenchmarkMake alloc_test.go:8
     .          .     4edaae: MOVQ 0x18(SP), AX                       ;alloc_test.go:8
  10ms       10ms     4edab3: MOVQ 0x20(SP), CX                       ;test.BenchmarkMake alloc_test.go:8
     .          .     4edab8: TESTQ CX, CX                             ;alloc_test.go:9
     .          .     4edabb: JBE 0x4edb0b
     .          .     4edabd: MOVQ $0x1, 0(AX)
 130ms      130ms     4edac4: CMPQ $0x1, CX                           ;test.BenchmarkMake alloc_test.go:10
     .          .     4edac8: JBE 0x4edb04                             ;alloc_test.go:10
     .          .     4edaca: MOVQ $0x1, 0x8(AX)
  20ms       20ms     4edad2: CMPQ $0x2, CX                           ;test.BenchmarkMake alloc_test.go:11
     .          .     4edad6: JBE 0x4edafd                             ;alloc_test.go:11
     .          .     4edad8: MOVQ $0x1, 0x10(AX)
     .          .     4edae0: CMPQ $0x3, CX                           ;alloc_test.go:12
     .          .     4edae4: JA 0x4eda65

我们可以看到CMPQ命令将时间与CX寄存器进行比较所花费的时间。 CX寄存器是调用make后从堆栈复制的值。我们可以推断它必须是切片的大小,而AX保存对底层数组的引用。您还可以看到第一个绑定检查已经过优化。

结论

  1. 分配需要相同的时间,但由于切片尺寸检查(由Terry Pang注意到),分配成本更高。
  2. 使用数组而不是切片会更便宜,因为它可以节省分配。
  3. 为什么使用数组这么便宜?

    在Go中,数组基本上是一块固定大小的内存。 [1]intint基本相同。您可以在Go Slices: usage and internals文章中找到更多信息。