设为首页 收藏本站
查看: 1167|回复: 0

[经验分享] [golang]内存不断增长bytes.makeSlice

[复制链接]

尚未签到

发表于 2018-9-20 07:40:47 | 显示全部楼层 |阅读模式
  ------------------------------------------
  2015.7月更新
  后面发现这里其实有一个sb的问题,在于内存回收和释放。
  每个http请求,都会带一个http.Request, 当请求并发数上来的时候,若不主动进行释放。垃圾回收机制会认为这个对象还不能回收。
  其实这里的本质问题,是一个http连接的生命周期是如何管理的,代码封装的太好,也需要知道里面如何实现啊(后面有空研究一下源码),不然还是会踩坑。
  

func Action(w http.ResponseWriter, r *http.Request) {  

  

  

var result string  
// handle func
  
// do something
  
w.Write([]byte(result))
  
r.Body.Close()
  
}
  

  

  
func (h *XXXXHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  
Action(w, r)
  
}
  

  ------------------------------------
  golang写的一个图片服务器,在批量下载压缩时候发现内存不断增长。。。。
  幸好golang自带内存占用日志结合分析工具可以方便看到内存分布。
  详细可参考:
  http://blog.golang.org/profiling-go-programs
  可以实时统计CPU\内存信息。
  这里主要说一下内存怎么搞。CPU分析的参考之前的一篇文章。
  

//需要包含这个pprof包  

import  "runtime/pprof"  

  

  
//这里接收内存统计信息保存文件
  
var memprofile = flag.String("memprofile", "", "write memory profile to this file")
  

  

  
//这里是判断是否需要记录内存的逻辑
  
if *memprofile != "" {
  
var err error
  
memFile, err = os.Create(*memprofile)
  
if err != nil {
  
log.Println(err)
  
} else {
  
log.Println("start write heap profile....")
  
pprof.WriteHeapProfile(memFile)
  
defer memFile.Close()
  
}
  
}
  

  
//这里还有一个比较灵活的办法,把开启记录和关闭记录作为http请求,需要的时候开启\不需要的时候关闭。记得加上token
  

  全部代码如下:
  

// GODEBUG=schedtrace=1000 ./trace_example  

// GOMAXPROCS=2 GODEBUG=schedtrace=1000 ./trace_example  

// GOMAXPROCS=2 GODEBUG=schedtrace=1000,scheddetail=1 ./trace_example  

  
package main
  

  
import (
  
"flag"
  
"log"
  
"os"
  
"runtime/pprof"
  
// "net/http"
  
// _ "net/http/pprof"
  
"sync"
  
"time"
  
)
  

  
//http://www.graphviz.org/Download_macos.php
  

  
// var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
  
var memprofile = flag.String("memprofile", "", "write memory profile to this file")
  

  
var memFile *os.File
  

  
func main() {
  
flag.Parse()
  
// if *cpuprofile != "" {
  
//     f, err := os.Create(*cpuprofile)
  
//     if err != nil {
  
//         log.Fatal(err)
  
//     }
  
//     pprof.StartCPUProfile(f)
  
//     defer pprof.StopCPUProfile()
  
// }
  

  
if *memprofile != "" {
  
var err error
  
memFile, err = os.Create(*memprofile)
  
if err != nil {
  
log.Println(err)
  
} else {
  
log.Println("start write heap profile....")
  
pprof.WriteHeapProfile(memFile)
  
defer memFile.Close()
  
}
  
}
  

  
// go func() {
  
//     log.Println(http.ListenAndServe("localhost:6060", nil))
  
// }()
  

  
var wg sync.WaitGroup
  
wg.Add(10)
  
for i := 0; i < 10; i++ {
  
go work(&wg)
  
}
  

  
wg.Wait()
  
// Wait to see the global run queue deplete.
  
time.Sleep(300 * time.Second)
  
}
  

  
func work(wg *sync.WaitGroup) {
  
time.Sleep(time.Second)
  

  
var counter int
  
for i := 0; i < 1e10; i++ {
  
time.Sleep(time.Millisecond * 100)
  
pprof.WriteHeapProfile(memFile)
  
counter++
  
}
  
wg.Done()
  
}
  

  OK,加上这个内存分析数据之后,继续跑服务, 跑了一段时候之后,停止程序,采用以下命令进行分析。
  

go tool pprof image_service  memory.log   

  

(pprof) top20  

2622.12MB of 4938.25MB total (53.10%)  
Dropped
180 nodes (cum = 419.23MB)  
flat  flat
%   sum%        cum   cum%  
1759.43MB 35.63% 35.63%  1759.43MB 35.63%  bytes.makeSlice
  
203.06MB  4.11% 39.74%   320.58MB  6.49%  net/url.parseQuery
  
166.11MB  3.36% 43.10%   166.11MB  3.36%  net/textproto.(*Reader).ReadLine
  
132.03MB  2.67% 45.78%   132.03MB  2.67%  net/textproto.(*Reader).ReadMIMEHeader
  
117.52MB  2.38% 48.16%   117.52MB  2.38%  net/url.unescape
  
71.02MB  1.44% 49.60%    71.02MB  1.44%  mcommoninit
  
60.50MB  1.23% 50.82%    60.50MB  1.23%  fmt.Sprintf
  
37.51MB  0.76% 51.58%    98.01MB  1.98%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).HandleRedo
  
35.51MB  0.72% 52.30%   333.65MB  6.76%  net/http.ReadRequest
  
21.37MB  0.43% 52.73%    21.37MB  0.43%  github.com/gographics/imagick/imagick._Cfunc_GoBytes
  
17.57MB  0.36% 53.09%    17.57MB  0.36%  bufio.NewReaderSize
  
0.50MB  0.01% 53.10%    21.58MB  0.44%  net/http.(*Transport).dialConn
  
0     0% 53.10%    21.87MB  0.44%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).CompressWithSizeList
  
0     0% 53.10%  1781.66MB 36.08%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).DoRecompress
  
0     0% 53.10%  1759.29MB 35.63%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).httpGetToMagickWand
  
0     0% 53.10%    17.57MB  0.36%  bufio.NewReader
  
0     0% 53.10%  1759.43MB 35.63%  bytes.(*Buffer).ReadFrom
  
0     0% 53.10%    21.37MB  0.43%  github.com/gographics/imagick/imagick.(*MagickWand).GetImageBlob
  
0     0% 53.10%   419.23MB  8.49%  main.(*ImageService).ServeHTTP
  
0     0% 53.10%   419.23MB  8.49%  main.Action
  
(pprof) quit
  

  初步可以定位到时下载压缩时,分配了太多byteSlice导致。
  观察代码,没有发现具体原因,直到在网上发现了这篇文章:
  http://openmymind.net/Go-Slices-And-The-Case-Of-The-Missing-Memory/
  

buffer := bytes.NewBuffer(make([]byte, 0, resp.ContentLength)  
buffer.ReadFrom(res.Body)
  
body :
= buffer.Bytes()  


A Memory Leak
  Look, what's a memory leak within the context of a runtime that provides garbage collection? Typically it's either a rooted object, or a reference from a rooted object, which you haven't considered. This is obviously different as it's really extra memory you might not be aware of. Rooting the object might very well be intentional, but you don't realize just how much memory it is you've rooted. Sure, my ignorance is at least 75% to blame. Yet I can't help but shake the feeling that there's something too subtle about all of this. Any code can return something that looks and quacks like an array of 2 integers yet takes gigs of memory. Furthermore, bytes.MinRead as a global variable is just bad design. I can't imagine how many people think they've allocated X when they've really allocated X*2+512.
  大致的意思是说,这个buffer采用最小单位读,若不够,则继续申请2倍大的空间。
  可以查看源码:
  

   146    // ReadFrom reads data from r until EOF and appends it to the buffer, growing  
147    // the buffer as needed. The return value n is the number of bytes read. Any
  
148    // error except io.EOF encountered during the read is also returned. If the
  
149    // buffer becomes too large, ReadFrom will panic with ErrTooLarge.
  
150    func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
  
151        b.lastRead = opInvalid
  
152        // If buffer is empty, reset to recover space.
  
153        if b.off >= len(b.buf) {
  
154            b.Truncate(0)
  
155        }
  
156        for {
  
157            if free := cap(b.buf) - len(b.buf); free < MinRead {
  
158                // not enough space at end
  
159                newBuf := b.buf
  
160                if b.off+free < MinRead {
  
161                    // not enough space using beginning of buffer;
  
162                    // double buffer capacity
  
163                   newBuf = makeSlice(2*cap(b.buf) + MinRead)
  
164                }
  
165                copy(newBuf, b.buf[b.off:])
  
166                b.buf = newBuf[:len(b.buf)-b.off]
  
167                b.off = 0
  
168            }
  
169            m, e := r.Read(b.buf[len(b.buf):cap(b.buf)])
  
170            b.buf = b.buf[0 : len(b.buf)+m]
  
171            n += int64(m)
  
172            if e == io.EOF {
  
173                break
  
174            }
  
175            if e != nil {
  
176                return n, e
  
177            }
  
178        }
  
179        return n, nil // err is EOF, so return nil explicitly
  
180    }
  

  解决方案:
  

//ioutil.ReadAll starts at a very small 512  

//it really should let you specify an initial>
buffer := bytes.NewBuffer(make([]byte, 0, 65536))  
io.Copy(buffer, r.Body)
  
temp := buffer.Bytes()
  
length := len(temp)
  
var body []byte
  
//are we wasting more than 10% space?
  
if cap(temp) > (length + length / 10) {
  
body = make([]byte, length)
  
copy(body, temp)
  
} else {
  
body = temp
  
}
  

  稍微测试了以下,内存被垃圾回收了。为啥会出现这样的情况呢?
  

Entering interactive mode (type "help" for commands)  
(pprof) top20
  

834.66MB of 1599.63MB total (52.18%)  
Dropped
175 nodes (cum = 72.01MB)  
flat  flat
%   sum%        cum   cum%  
427.45MB 26.72% 26.72%   427.45MB 26.72%  bytes.makeSlice
  
185.80MB 11.62% 38.34%   614.25MB 38.40%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).httpGetToMagickWand
  
69.01MB  4.31% 42.65%    69.01MB  4.31%  net/textproto.(*Reader).ReadMIMEHeader
  
48MB  3.00% 45.65%       48MB  3.00%  net/url.unescape
  
24.51MB  1.53% 47.18%    24.51MB  1.53%  mcommoninit
  
24.01MB  1.50% 48.68%    72.01MB  4.50%  net/url.parseQuery
  
24MB  1.50% 50.19%   117.02MB  7.32%  net/http.ReadRequest
  
24MB  1.50% 51.69%       24MB  1.50%  net/url.parse
  
7.87MB  0.49% 52.18%     7.87MB  0.49%  github.com/gographics/imagick/imagick._Cfunc_GoBytes
  
0     0% 52.18%     7.87MB  0.49%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).CompressWithSizeList
  
0     0% 52.18%   622.62MB 38.92%  _/home/qingpingzhang/project/createdji_servers/image_service/image.(*Handler).DoRecompress
  
0     0% 52.18%   427.95MB 26.75%  bytes.(*Buffer).ReadFrom
  
0     0% 52.18%     7.87MB  0.49%  github.com/gographics/imagick/imagick.(*MagickWand).GetImageBlob
  
0     0% 52.18%    72.01MB  4.50%  main.(*ImageService).ServeHTTP
  
0     0% 52.18%    72.01MB  4.50%  main.Action
  
0     0% 52.18%    72.01MB  4.50%  net/http.(*Request).ParseForm
  
0     0% 52.18%   117.02MB  7.32%  net/http.(*conn).readRequest
  
0     0% 52.18%   117.02MB  7.32%  net/http.(*conn).serve
  
0     0% 52.18%    72.01MB  4.50%  net/http.func·014
  
0     0% 52.18%    72.01MB  4.50%  net/url.ParseQuery
  

  在golang语言自带的bytes包里面申请的内存,为啥就不会很快被回收?
  不解,IO操作这块儿还需要找时间重新学习一下。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-597885-1-1.html 上篇帖子: [Golang]实习最后一天小纪念+并发爬虫小练习 下篇帖子: [golang note] 函数定义
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表