Go调优过程(持续更新中)

进入正题前,请允许我打个广告
阿里云2018双11云服务只需99.5元

  • 1核2G内存,¥99.5/年
  • 2核4G内存,¥545.00/1年
  • 2核4G内存,¥927.00/2年
  • 2核4G内存,¥1227.00/3年
  • 2核8G内存,¥2070.00/3年(本人认为最划算)
    直达入口:http://t.cn/EZ14u8r
1. 火焰图 pprof

go1.11 后自带了 pprof 工具,在 $GOPATH/bin 下可找到。

1.1 pprof基础知识

pprof允许收集Go程序CPU、栈和堆信息等,这些信息在后文中,还是沿用官方名词 profile。使用pprof的正常方法是:

  1. 设置Web服务器以获取Go profile(带import _ "net/http/pprof"
  2. 运行curl localhost:$PORT/debug/pprof/$PROFILE_TYPE以保存 profile
  3. 使用go tool pprof分析 profile

您也可以使用pprof在代码中生成pprof profile,但我还没有这样做。

1.2 有用的pprof阅读

以下是我迄今为止在互联网上发现的关于pprof的所有有用链接。基本上互联网上关于pprof的材料似乎是官方文档+ rakyll的惊人博客。

(可能还有关于pprof的讨论,但是我太急于观看会谈,这也是为什么我写了很多博客文章并且几乎没有谈判的部分原因)

什么是个人资料?我可以获得哪些类型的profile?

在了解事情如何运作时,我喜欢从头开始。究竟什么是“个人资料”?

好吧,让我们阅读文档吧!我第7次查看运行时/ pprof文档时,我读到了这个非常有用的句子:

profile是堆栈跟踪的集合,显示导致特定事件(例如分配)实例的调用序列。包可以创建和维护自己的profile; 最常见的用途是跟踪必须明确关闭的资源,例如文件或网络连接。

每个profile都有唯一的名称。预定义了一些profile:

goroutine    - stack traces of all current goroutines
heap         - a sampling of all heap allocations
threadcreate - stack traces that led to the creation of new OS threads
block        - stack traces that led to blocking on synchronization primitives
mutex        - stack traces of holders of contended mutexes

您可以在默认网络服务器中获取7个地方:上面提到的那些

还有2个:CPU profile和CPU trace。

要分析这些profile(堆栈跟踪列表),要使用的工具是go tool pprof,这是一组用于可视化堆栈跟踪的工具。

超级混乱的注意事项:跟踪端点(/debug/pprof/trace?seconds=5)与其他所有内容不同,输出的文件不是 pprof profile。相反,它是一个跟踪,您可以使用go tool trace(而不是go tool pprof)查看它。

您可以在浏览器中看到http:// localhost:6060 / debug / pprof /的可用profile。除非它没有告诉你/debug/pprof/profile/debug/pprof/trace由于某种原因。

所有这些类型的profile(goroutine,堆分配等)都只是堆栈跟踪的集合,可能附加了一些元数据。如果我们看一下pprof protobuf定义,你会发现一个profile主要是一堆Samples。

样本基本上是堆栈跟踪。堆栈跟踪可能附加了一些额外的信息!例如,在堆profile中,堆栈跟踪具有附加到其上的多个字节的内存。我认为样本是概要中最重要的部分。

我们稍后将解构pprof文件中究竟是什么,但是现在让我们首先做一个分析堆profile的快速示例!

使用pprof获取堆profile

我现在最感兴趣的是调试内存问题。所以我决定编写一个程序,用pprof分配一堆内存来profile。

func main() {
    // we need a webserver to get the pprof webserver
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()
    fmt.Println("hello world")
    var wg sync.WaitGroup
    wg.Add(1)
    go leakyFunction(wg)
    wg.Wait()
}

func leakyFunction(wg sync.WaitGroup) {
    defer wg.Done()
    s := make([]string, 3)
    for i:= 0; i < 10000000; i++{
        s = append(s, "magical pandas")
        if (i % 100000) == 0 {
            time.Sleep(500 * time.Millisecond)
        }
    }
}

基本上这只是启动一个goroutine leakyFunction,分配一堆内存,然后最终退出。

获取此程序的堆profile非常简单 - 我们只需运行即可go tool pprof http://localhost:6060/debug/pprof/heap。这使我们进入了一个我们运行的交互模式top

$ go tool pprof  http://localhost:6060/debug/pprof/heap
    Fetching profile from http://localhost:6060/debug/pprof/heap
    Saved profile in /home/bork/pprof/pprof.localhost:6060.inuse_objects.inuse_space.004.pb.gz
    Entering interactive mode (type "help" for commands)
(pprof) top
    34416.04kB of 34416.04kB total (  100%)
    Showing top 10 nodes out of 16 (cum >= 512.04kB)
          flat  flat%   sum%        cum   cum%
       33904kB 98.51% 98.51%    33904kB 98.51%  main.leakyFunction

我也可以在交互模式之外做同样的事情go tool pprof -top http://localhost:6060/debug/pprof/heap

这基本上告诉我们main.leakyFunction使用339MB的内存。整齐!

我们还可以生成这样的PNGprofile:go tool pprof -png http://localhost:6060/debug/pprof/heap > out.png

这就是它的样子(我在不同的时间运行它,所以它只使用100MB内存)。

image

堆profile中的堆栈跟踪意味着什么?

这并不复杂,但对我来说也不是100%明显。堆profile中的堆栈跟踪是分配时的堆栈跟踪。

因此,堆profile中的堆栈跟踪可能适用于不再运行的代码 - 例如,可能是一个分配了一堆内存,返回的函数,以及应该释放内存不正常的不同函数。因此,内存泄漏的责任函数可能与堆profile中列出的函数完全不同。

alloc_space vs inuse_space

go tool pprof可以选择显示分配计数使用内存。如果你关心的内存量被使用,你可能想的INUSE指标,但如果你担心垃圾收集所花费的时间,看一下分配!

  -inuse_space      Display in-use memory size
  -inuse_objects    Display in-use object counts
  -alloc_space      Display allocated memory size
  -alloc_objects    Display allocated object counts

我最初对此作品感到困惑 - 已经收集了个人资料!事后我怎么能做出这个选择呢?我认为堆profile的工作原理是 - 以某种采样率记录分配。然后每次释放其中一个分配时,也会记录下来。因此,您可以获得某些内存活动样本的分配和释放历史记录。然后,当分析您的内存使用情况时,您可以决定在哪里使用内存或总分配计数!

您可以在此处阅读内存分析器的源代码:https//golang.org/src/runtime/mprof.go。它有很多有用的评论!例如,以下是有关设置采样率的注释:

// MemProfileRate controls the fraction of memory allocations
// that are recorded and reported in the memory profile.
// The profiler aims to sample an average of
// one allocation per MemProfileRate bytes allocated.

// To include every allocated block in the profile, set MemProfileRate to 1.
// To turn off profiling entirely, set MemProfileRate to 0.

// The tools that process the memory profiles assume that the
// profile rate is constant across the lifetime of the program
// and equal to the current value. Programs that change the
// memory profiling rate should do so just once, as early as
// possible in the execution of the program (for example,
// at the beginning of main).

pprof基础:解构pprof文件

当我开始使用pprof时,我对实际发生的事情感到困惑。它生成了这些名为like的堆profilepprof.localhost:6060.inuse_objects.inuse_space.004.pb.gz - 这是什么?我怎样才能看到内容?

好吧,我们来看看!! 我写了一个更简单的Go程序来获得最简单的堆profile。

package main

import "runtime"
import "runtime/pprof"
import "os"
import "time"

func main() {
    go leakyFunction()
    time.Sleep(500 * time.Millisecond)
    f, _ := os.Create("/tmp/profile.pb.gz")
    defer f.Close()
    runtime.GC()
    pprof.WriteHeapProfile(f);
}

func leakyFunction() {
    s := make([]string, 3)
    for i:= 0; i < 10000000; i++{
        s = append(s, "magical pprof time")
    }
}

该程序只分配一些内存,写入堆profile,然后退出。很简单。我们来看看这个文件/tmp/profile.pb.gz吧!你可以profile.pb 在这里下载一个gunzipped版本:profile.pb。我使用这些指示安装了protoc 。

profile.pb是一个protobuf文件,事实证明你可以使用protocprotobuf编译器查看protobuf文件。

go get github.com/google/pprof/proto
protoc --decode=perftools.profiles.Profile  $GOPATH/src/github.com/google/pprof/proto/profile.proto --proto_path $GOPATH/src/github.com/google/pprof/proto/

这个输出有点长,你可以在这里查看:输出

以下是此profile文件中的内容摘要!这包含1个样本。样本是堆栈跟踪,此堆栈跟踪有2个位置:1和2.什么是位置1和2?它们对应于映射1和2,映射又对应于文件名7和8。

如果我们查看字符串表,我们会看到文件名7和8是这两个:

string_table: "/home/bork/work/experiments/golang-pprof/leak_simplest"
string_table: "[vdso]"

sample {
  location_id: 1
  location_id: 2
  value: 1
  value: 34717696
  value: 1
  value: 34717696
}
mapping {
  id: 1
  memory_start: 4194304
  memory_limit: 5066752
  filename: 7
}
mapping {
  id: 2
  memory_start: 140720922800128
  memory_limit: 140720922808320
  filename: 8
}
location {
  id: 1
  mapping_id: 1
  address: 5065747
}
location {
  id: 2
  mapping_id: 1
  address: 4519969
}
string_table: ""
string_table: "alloc_objects"
string_table: "count"
string_table: "alloc_space"
string_table: "bytes"
string_table: "inuse_objects"
string_table: "inuse_space"
string_table: "/home/bork/work/experiments/golang-pprof/leak_simplest"
string_table: "[vdso]"
string_table: "[vsyscall]"
string_table: "space"
time_nanos: 1506268926947477256
period_type {
  type: 10
  unit: 4
}
period: 524288

pprof文件并不总是包含函数名称

关于这个pprof文件的一个有趣的事情profile.pb是它不包含我们正在运行的函数的名称!但如果我运行go tool pprof它,它会打印出泄漏函数的名称。你是怎么做到的,go tool pprof?!

go tool pprof -top  profile.pb 
59.59MB of 59.59MB total (  100%)
      flat  flat%   sum%        cum   cum%
   59.59MB   100%   100%    59.59MB   100%  main.leakyFunction
         0     0%   100%    59.59MB   100%  runtime.goexit

我用strace回答了这个问题,很明显 - 我stra go tool pprofand这就是我所看到的:

5015  openat(AT_FDCWD, "/home/bork/pprof/binaries/leak_simplest", O_RDONLY|O_CLOEXEC <unfinished ...>
5015  openat(AT_FDCWD, "/home/bork/work/experiments/golang-pprof/leak_simplest", O_RDONLY|O_CLOEXEC) = 3

因此,似乎go tool pprof注意到文件名profile.pb是/ home / bork / work / experiments / golang -pprof / leak_simplest,然后它只是在我的计算机上打开该文件并使用它来获取函数名称。整齐!

您也可以将二进制文件传递给go tool pprof喜欢go tool pprof -out $BINARY_FILE myprofile.pb.gz。有时pprof文件包含函数名称,有时它们没有,我还没弄清楚是什么决定了它。

pprof不断改进!

我也发现,感谢rakyll等人的出色工作,pprof不断变得更好!例如,有这个拉取请求https://github.com/google/pprof/pull/188正在正确使用,它为pprof web界面添加了火焰图支持。火焰图是宇宙中最好的东西,所以我很高兴能够获得它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值