【问题标题】:golang slice allocation performancegolang 切片分配性能
【发布时间】:2016-07-07 15:50:36
【问题描述】:

我在检查 GO 中的内存分配性能时偶然发现了一件有趣的事情。

package main

import (
      "fmt"
      "time"
    )

func main(){
   const alloc int = 65536
   now := time.Now()
   loop := 50000
   for i := 0; i<loop;i++{
      sl := make([]byte, alloc)
      i += len(sl) * 0
   }
   elpased := time.Since(now)
   fmt.Printf("took %s to allocate %d bytes %d times", elpased, alloc, loop) 
}

我在 Core-i7 2600 上运行它,Go 版本 1.6 64 位(在 32 位上也有相同的结果)和 16GB 内存(在 WINDOWS 10 上) 因此,当 alloc 为 65536(正好是 64K)时,它会运行 30 秒(!!!!!!)。 当 alloc 为 65535 时,大约需要 200 毫秒。 有人可以向我解释一下吗? 我在家里用我的核心 i7-920 @ 3.8GHZ 尝试了相同的代码,但它没有显示相同的结果(都花了大约 200 毫秒)。有人知道发生了什么吗?

【问题讨论】:

  • 要添加更多变化,在 Windows 7(Go 1.6,64 位)上尝试您的代码,无论 alloc65536 还是 65535,我都会得到 17 秒。跨度>
  • 我不是分配内部的专家,但我只想提一下,分配一个 65536 字节的切片实际上是分配它加上 2 个整数(lencap 计数器),所以实际上超过 64KB。
  • 为了给问题添加更多信息,我在我的 Archlinux (i7-4720HQ @ 2.60GHz) 上运行了代码,它持续花费了大约 600 毫秒。您应该尝试在需要很长时间的设置上使用分析工具。如果您还不知道它实际上是开始学习它的好案例。
  • 您可以尝试禁用垃圾收集器 (GOGC=off) 看看是否是问题所在。使用 16 GB 的 RAM,您应该有足够的内存。
  • 是的。 set GOGC=off 确实提高了性能。所以我想我现在明白发生了什么。由于逃逸分析,golang 在堆上分配内存,然后 gc 需要清理它。当我分配少于 64K 时,go 使用堆栈。当数组在堆栈上时,它会自行清理,并且分配只需要一条 CPU 指令(只需创建一个指向堆栈上某处的指针)。

标签: performance memory go


【解决方案1】:

设置 GOGC=off 可提高性能(降至 100 毫秒以下)。为什么? 因为escape analysis。当您使用go build -gcflags -m 构建时,编译器会打印任何逃逸到堆的分配。这实际上取决于您的机器和 GO 编译器版本,但是当编译器决定分配应该移动到堆时,这意味着两件事: 1.分配将花费更长的时间(因为堆栈上的“分配”只是1个cpu指令) 2. GC 将不得不稍后清理该内存 - 花费更多 CPU 时间 对于我的机器,65536 字节的分配会转义到堆,而 65535 不会。 这就是为什么 1 个字节将整个过程从 200 毫秒更改为 30 秒。太棒了..

【讨论】:

    【解决方案2】:

    原因很简单。

    const alloc int = 65535

    0x0000 00000 (example.go:8) TEXT "".main(SB), ABIInternal, $65784-0

    const alloc int = 65536

    0x0000 00000 (example.go:8) TEXT "".main(SB), ABIInternal, $248-0

    不同之处在于创建切片的位置。

    【讨论】:

      【解决方案3】:

      注意/2021 年更新:作为 Tapir Liui 中的注释 Go101this tweet

      从 Go 1.17 开始,如果编译器证明它们仅用于当前的 goroutine 和 N &lt;= 64KB,则 Go 运行时将在堆栈上分配 slice x 的元素:

      var x = make([]byte, N)
      

      如果编译器证明它仅用于当前goroutine和N &lt;= 10MB,Go运行时将在堆栈上分配数组 y

      var y [N]byte
      

      那么如何分配一个sliceslice栈上大小大于64KB但不大于10MB(并且该slice只用在一个协程)?

      只需使用以下方式:

      var y [N]byte
      var x = y[:]
      

      考虑到堆栈分配比堆分配快,这将对您的测试产生直接影响,因为alloc 等于 65536 甚至更多。

      adds:

      事实上,我们可以在堆栈上分配具有任意总和元素大小的切片。

      const N = 500 * 1024 * 1024 // 500M
      var v byte = 123
      
      func createSlice() byte {
       var s = []byte{N: 0}
       for i := range s { s[i] = v }
       return s[v]
      }
      

      将 500 更改为 512 会使程序崩溃。

      【讨论】:

        猜你喜欢
        • 2016-12-03
        • 2016-02-20
        • 2018-04-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-17
        • 1970-01-01
        • 2019-03-07
        相关资源
        最近更新 更多