爱上开源之golang入门至实战第三章-内存Alloc分析

爱上开源之golang入门至实战第三章-内存Alloc分析

 

Memory Allocs

 

Allocs也是关注与内存方面的数据采样,而且由于Allocs的采样数据和Heap的采样数据大致上都是一样的。

所以很多golang的开发人员非常容易忽略这个Allocs的数据采样

 

 

上面就是Allocs的数据采样的文本格式的内容; 和Heap的对照一下,确实发现是一致的; 我们可以详细查看pprof输出allocs的源代码runtime/pprof/pprof.go line 546 行函数writeHeapInternal的实现。可以发现Heap和Allocs两者都是使用同一个函数进行输出的;在同一个时间点的采样上,输出的数据就是一致的。

虽然Heap和Allocs两者的数据样本一致;但是在pprof进行分析的时候,两者所主要针对的方向是完全不同的方向; 如同pprof的index页面里描述的一般:

我们来看看

 

allocs: A sampling of all past memory allocations.

heap: A sampling of memory allocations of live objects.

Heap分析的最要采样对象是采样时间点时的活动对象(未销毁)的数据样本;而Allocs分析的主要采用对象是至采用时间点过去时间里的内存分配的数据样本;这个内存分配的样本对于的内存有可能已经销毁了,也有可能仍然存在;Allocs更多的关注的是内存申请和分配的过程;Heap关注的内存申请和分配的结果;

上一个章节里;我们讲解了Heap的数据分析过程;通过Heap的数据分析;我们可以有依据的对内存不释放或者内存溢出的可能性的地方进行判断和分析; 是个非常不错的内存诊断的方法; 这个Allocs的数据分析;又能给我们带来什么样的性能和内存分析方法,我们就一起来看看吧。

Allocs的手工埋点

虽然如上所述; Allocs和Heap的输出的代码是完全同样的一套代码,但是在我们对Allocs进行埋点的时候,可以完全使用和上一节一模一样的调用方式; 两者输出一模一样; 在需要采样的时间点上执行,下面代码即可:

    if err := pprof.WriteHeapProfile(f1); err != nil {
        panic("could not start heap-1 profile: ")
    }

上面的这个方法虽然能够到达同样的效果,但是是阅读了pprof的源码后;发现输出是一致的;这样情况下的偷巧的方法;如果以后golang的pprof升级,就可能有问题了;这里推荐使用标准allocs埋点;

    allocProfile := pprof.Lookup("allocs")
​
    f3, err := os.Create("alloc-prof-3")
    if err != nil {
        panic(err)
    }
    defer f3.Close()
    
    if err := allocProfile.WriteTo(f3, 0); err != nil {
        panic("could not start alloc-3 profile: ")
    }
​

网上的相关资料非常的少; 上述有关allocs的手工埋点的方法;是阅读pprof的相关源代码以后;经过测试是完全可行的方法;

pprof工具Allocs分析

运行命令

go tool pprof http://localhost:8999/debug/pprof/allocs

 

图中是直接从本地埋点产生的alloc-prof-3这个采样文件里直接进行本地分析的; 和go tool pprof http://localhost:8999/debug/pprof/allocs的方法没什么差别,只是数据源来源不一样而已; 下面所讲到的分析方法和过程,均不受此影响

Top命令

 

Top命令的结果,列表出采集到的发生了内存分配的函数调用;

其中每一行都表示一个采集到的函数调用,

每列都对应着采集到的数据分析; 列的参照如下

flat:函数在调用中内存分配的数量

flat%:函数在调用中内存分配的百分比

sum%:所有函数累加使用内存分配的比例,即所有flat%的总和

cum: 函数以及子函数运行所使用内存分配,应该大于等于flat

cum%: 函数以及子函数运行所使用内存分配的比例,应该大于等于flat%

函数的名字

这里可以看到调用现在内存分配最多是分配了1.07G, 占所有内存分配的99.54%,函数go-in-practice/code/charpter-01.TestAllocPprof, 这个函数的调用占用整个程序运行过程中所有的的内存分配, 这个函数究竟做了哪些动作,导致内存分配如此之高, 我们可以在pprof里运行list命令, 进一步进行分析

使用pprof命令:list 函数名。 可以用pprof分析函数中的哪一行导致的内存占用

(pprof) list TestAllocPprof
Total: 1.07GB
ROUTINE ======================== go-in-practice/code/charpter-01.TestAllocPprof in E:\WORK\PROJECT\git\go\go-in-practice\code\charpter-01\lesson02_test.go
         .          .    183:   for i := 0; i < poolSize; i++ {
   99.47MB    99.47MB    184:           pool = make([]byte, poolSize)
         .          .    185:           sum += poolSize
         .          .    186:           pool[0] = byte('c')
         .          .    187:   }
         .          .    188:
         .     2.09MB    189:   if err := allocProfile.WriteTo(f2, 0); err != nil {
         .          .    190:           panic("could not start alloc-2 profile: ")
         .          .    191:   }
         .          .    192:
         .          .    193:   var pool2 []byte
         .          .    194:   for i := 0; i < poolSize/10; i++ {
  993.44MB   993.44MB    195:           pool2 = make([]byte, poolSize*100)
         .          .    196:           sum += poolSize * 10
         .          .    197:           pool2[0] = byte('c')
         .          .    198:   }
         .          .    199:
         .          .    200:   if err := allocProfile.WriteTo(f3, 0); err != nil {

通过这个命令,可以直接追击到源代码的行数,从而进行仔细的调用过程分析

 

代码195行:

pool2 = make([]byte, poolSize*100)

make([]byte) 进行了内存是申请的操作, 在外部的for循环里,循环的进行申请,申请的数量达到了1G左右。

使用图形化web命令进行分析

在pprof的交互模式里输入web命令

 

使用tree命令查看调用关系

(pprof) tree
Showing nodes accounting for 1.07GB, 99.54% of 1.07GB total                                               
Dropped 24 nodes (cum <= 0.01GB)                                                                          
----------------------------------------------------------+-------------                                  
      flat  flat%   sum%        cum   cum%   calls calls% + context                                       
----------------------------------------------------------+-------------                                  
                                            1.07GB   100% |   testing.tRunner                             
    1.07GB 99.54% 99.54%     1.07GB 99.73%                | go-in-practice/code/charpter-01.TestAllocPprof
----------------------------------------------------------+-------------                                  
         0     0% 99.54%     1.07GB 99.73%                | testing.tRunner                               
                                            1.07GB   100% |   go-in-practice/code/charpter-01.TestAllocPprof
----------------------------------------------------------+-------------
​
​

使用traces命令查看采样数据

(pprof) traces
Type: alloc_space
Time: Jul 5, 2022 at 9:08am (CST)
-----------+-------------------------------------------------------
     bytes:  1000kB
  993.44MB   go-in-practice/code/charpter-01.TestAllocPprof
             testing.tRunner
-----------+-------------------------------------------------------
     bytes:  256kB
  650.62kB   compress/flate.(*compressor).init
             compress/flate.NewWriter
             compress/gzip.(*Writer).Write
             runtime/pprof.(*profileBuilder).build
             runtime/pprof.writeHeapProto
             runtime/pprof.writeHeapInternal
             runtime/pprof.writeAlloc
             runtime/pprof.(*Profile).WriteTo
             go-in-practice/code/charpter-01.TestAllocPprof
             testing.tRunner
-----------+-------------------------------------------------------
     bytes:  136kB
  583.01kB   compress/flate.newDeflateFast
             compress/flate.(*compressor).init
             compress/flate.NewWriter
             compress/gzip.(*Writer).Write
             runtime/pprof.(*profileBuilder).build
             runtime/pprof.writeHeapProto
             runtime/pprof.writeHeapInternal
             runtime/pprof.writeAlloc
             runtime/pprof.(*Profile).WriteTo
             go-in-practice/code/charpter-01.TestAllocPprof
             testing.tRunner
-----------+-------------------------------------------------------
     bytes:  648kB
  902.59kB   compress/flate.NewWriter
             compress/gzip.(*Writer).Write
             runtime/pprof.(*profileBuilder).build
             runtime/pprof.writeHeapProto
             runtime/pprof.writeHeapInternal
             runtime/pprof.writeAlloc
             runtime/pprof.(*Profile).WriteTo
             go-in-practice/code/charpter-01.TestAllocPprof
             testing.tRunner
-----------+-------------------------------------------------------
     bytes:  1kB
       1MB   runtime.allocm
             runtime.newm
             runtime.startm
             runtime.wakep
             runtime.resetspinning
             runtime.schedule
             runtime.park_m
             runtime.mcall
-----------+-------------------------------------------------------
     bytes:  10kB
   99.47MB   go-in-practice/code/charpter-01.TestAllocPprof
             testing.tRunner
-----------+-------------------------------------------------------
             runtime.schedule
             runtime.mstart1
             runtime.mstart0
             runtime.mstart
-----------+-------------------------------------------------------
     bytes:  416B
       1MB   runtime.malg
             runtime.newproc1
             runtime.newproc.func1
             runtime.systemstack
-----------+-------------------------------------------------------

可以看到这里内存分配了1G;但是活动的内存对象只有10K和1000K; 在这样的情况下,我们可以看到,以上场景的时候;每一次修改pool对象的时候,都重新的进行了内存申请;我们可以通过建立对象池的方式,来降低内存申请数量; 对当前这样场景的进行优化;在fasthttp项目中; 就大量使用了池化的实现, 来对一些操作频繁的对象类型进行了池化; 每次需要构建一个新对象时,在对象池里进行查询,如果有没有使用到的对象,就拿到空闲对象,然后对空闲对象进行初始化,从而不需要重新去对对象进行结构体的内存申请;通过这样的优化方式;fasthttp在大并发的情况下,依然能够保证非常高的响应要求;当然;时间和空间在优化的过程中,往往是对矛盾体;比如我们这里提到的Heap和Allocs; fasthttp的这种方式; 降低了Allocs的次数;但是就会提高Heap的大小; 如果非频繁调用, 这两者之间的差异不会很明显; 但是如果频繁调用的话, 优化前后的差异一定是越来越明显。

Allocs的pprof样本中的数据项

通过http://localhost:8999/debug/pprof/可以查看allocs的信息

 

 

技巧

这些信息是样本中的数据项,相对于我们通过web访问时,访问当前时间的heap的一个快照; 熟悉这些数据项所表示的含义,也非常对我们了解当前程序运行时的内存情况非常有帮助。

这里对应的样本的输出的源代码可以参考net/http/pprof/pprof.go, runtime/pprof/pprof.goruntime/mstats.go

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

inthirties

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值