Go:内存使用过多、内存泄漏

Ala*_*air 5 garbage-collection memory-leaks go

我非常非常小心记忆,因为我必须编写需要处理大量数据集的程序。

\n\n

目前我的应用程序很快就达到了 32GB 内存,开始交换,然后被系统杀死。

\n\n

我不明白这是怎么回事,因为除了结构体之外,所有变量都是可收集的(在函数中并快速TokensStruct释放TokensCountTrainerTokensCount只是一个单位。TokensStruct是 [5]uint32 和字符串的 1,000,000 行切片,因此这意味着 20 个字节 + 字符串,我们可以称之为每条记录最多 50 个字节。50*1000000 = 需要 50MB 内存。因此,该脚本不应在函数中使用超过 50MB + 开销 + 临时可收集变量(最多可能还有 50MB)。 的最大潜在大小TokensStruct是 5,000,000,因为这是 的大小dictionary,但即使如此,它也只有 250MB的记忆。dictionary是一张地图,显然使用了大约 600MB 的内存,因为这就是应用程序的启动方式,但这不是问题,因为dictionary只加载一次,再也不会写入。

\n\n

相反,它使用 32GB 内存然后就死掉了。从它执行此操作的速度来看,如果可以的话,我预计它会很乐意达到 1TB 内存。内存似乎随着加载文件的大小以线性方式增加,这意味着它似乎永远不会清除任何内存。进入应用程序的所有内容都会分配更多内存,并且内存永远不会被释放。

\n\n

我尝试实施runtime.GC()以防垃圾收集运行得不够频繁,但这没有什么区别。

\n\n

由于内存使用量以线性方式增加,因此这意味着GetTokens()或中存在内存泄漏LoadZip()。我不知道这是怎么回事,因为它们都是函数,只执行一项任务然后关闭。或者可能是tokensin 中的变量Start()是泄漏的原因。基本上,看起来加载和解析的每个文件都永远不会从内存中释放,因为这是内存以线性方式填满并继续上升到 32GB++ 的唯一方法。

\n\n

绝对的噩梦!Go 有什么问题吗?有任何解决这个问题的方法吗?

\n\n
package main\n\nimport (\n    "bytes"\n    "code.google.com/p/go.text/transform"\n    "code.google.com/p/go.text/unicode/norm"\n    "compress/zlib"\n    "encoding/gob"\n    "fmt"\n    "github.com/AlasdairF/BinSearch"\n    "io/ioutil"\n    "os"\n    "regexp"\n    "runtime"\n    "strings"\n    "unicode"\n    "unicode/utf8"\n)\n\ntype TokensStruct struct {\n    binsearch.Key_string\n    Value [][5]uint32\n}\n\ntype Trainer struct {\n    Tokens      TokensStruct\n    TokensCount uint\n}\n\nfunc checkErr(err error) {\n    if err == nil {\n        return\n    }\n    fmt.Println(`Some Error:`, err)\n    panic(err)\n}\n\n// Local helper function for normalization of UTF8 strings.\nfunc isMn(r rune) bool {\n    return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks\n}\n\n// This map is used by RemoveAccents function to convert non-accented characters.\nvar transliterations = map[rune]string{\'\xc3\x86\': "E", \'\xc3\x90\': "D", \'\xc5\x81\': "L", \'\xc3\x98\': "OE", \'\xc3\x9e\': "Th", \'\xc3\x9f\': "ss", \'\xc3\xa6\': "e", \'\xc3\xb0\': "d", \'\xc5\x82\': "l", \'\xc3\xb8\': "oe", \'\xc3\xbe\': "th", \'\xc5\x92\': "OE", \'\xc5\x93\': "oe"}\n\n//  removeAccentsBytes converts accented UTF8 characters into their non-accented equivalents, from a []byte.\nfunc removeAccentsBytesDashes(b []byte) ([]byte, error) {\n    mnBuf := make([]byte, len(b))\n    t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)\n    n, _, err := t.Transform(mnBuf, b, true)\n    if err != nil {\n        return nil, err\n    }\n    mnBuf = mnBuf[:n]\n    tlBuf := bytes.NewBuffer(make([]byte, 0, len(mnBuf)*2))\n    for i, w := 0, 0; i < len(mnBuf); i += w {\n        r, width := utf8.DecodeRune(mnBuf[i:])\n        if r == \'-\' {\n            tlBuf.WriteByte(\' \')\n        } else {\n            if d, ok := transliterations[r]; ok {\n                tlBuf.WriteString(d)\n            } else {\n                tlBuf.WriteRune(r)\n            }\n        }\n        w = width\n    }\n    return tlBuf.Bytes(), nil\n}\n\nfunc LoadZip(filename string) ([]byte, error) {\n    // Open file for reading\n    fi, err := os.Open(filename)\n    if err != nil {\n        return nil, err\n    }\n    defer fi.Close()\n    // Attach ZIP reader\n    fz, err := zlib.NewReader(fi)\n    if err != nil {\n        return nil, err\n    }\n    defer fz.Close()\n    // Pull\n    data, err := ioutil.ReadAll(fz)\n    if err != nil {\n        return nil, err\n    }\n    return norm.NFC.Bytes(data), nil // return normalized\n}\n\nfunc getTokens(pibn string) []string {\n    var data []byte\n    var err error\n    data, err = LoadZip(`/storedir/` + pibn + `/text.zip`)\n    checkErr(err)\n    data, err = removeAccentsBytesDashes(data)\n    checkErr(err)\n    data = bytes.ToLower(data)\n    data = reg2.ReplaceAll(data, []byte("$2")) // remove contractions\n    data = reg.ReplaceAllLiteral(data, nil)\n    tokens := strings.Fields(string(data))\n    return tokens\n}\n\nfunc (t *Trainer) Start() {\n    data, err := ioutil.ReadFile(`list.txt`)\n    checkErr(err)\n    pibns := bytes.Fields(data)\n    for i, pibn := range pibns {\n        tokens := getTokens(string(pibn))\n        t.addTokens(tokens)\n        if i%100 == 0 {\n            runtime.GC() // I added this just to try to stop the memory craziness, but it makes no difference\n        }\n    }\n}\n\nfunc (t *Trainer) addTokens(tokens []string) {\n    for _, tok := range tokens {\n        if _, ok := dictionary[tok]; ok {\n            if indx, ok2 := t.Tokens.Find(tok); ok2 {\n                ar := t.Tokens.Value[indx]\n                ar[0]++\n                t.Tokens.Value[indx] = ar\n                t.TokensCount++\n            } else {\n                t.Tokens.AddKeyAt(tok, indx)\n                t.Tokens.Value = append(t.Tokens.Value, [5]uint32{0, 0, 0, 0, 0})\n                copy(t.Tokens.Value[indx+1:], t.Tokens.Value[indx:])\n                t.Tokens.Value[indx] = [5]uint32{1, 0, 0, 0, 0}\n                t.TokensCount++\n            }\n        }\n    }\n    return\n}\n\nfunc LoadDictionary() {\n    dictionary = make(map[string]bool)\n    data, err := ioutil.ReadFile(`dictionary`)\n    checkErr(err)\n    words := bytes.Fields(data)\n    for _, word := range words {\n        strword := string(word)\n        dictionary[strword] = false\n    }\n}\n\nvar reg = regexp.MustCompile(`[^a-z0-9\\s]`)\nvar reg2 = regexp.MustCompile(`\\b(c|l|all|dall|dell|nell|sull|coll|pell|gl|agl|dagl|degl|negl|sugl|un|m|t|s|v|d|qu|n|j)\'([a-z])`) //contractions\nvar dictionary map[string]bool\n\nfunc main() {\n    trainer := new(Trainer)\n    LoadDictionary()\n    trainer.Start()\n}\n
Run Code Online (Sandbox Code Playgroud)\n

fra*_*lin 0

1 “list.txt”和“字典”有多大?如果这么大,难怪内存这么大

 pibns := bytes.Fields(data)
Run Code Online (Sandbox Code Playgroud)

多少钱len(pibns)

2 启动 gc 调试( do GODEBUG="gctrace=1" ./yourprogram )以查看是否有任何 gc 发生

3 做一些像这样的配置文件:

    func lookupMem(){
      if f, err := os.Create("mem_prof"+time.Now.Unix()); err != nil {
          log.Debug("record memory profile failed: %v", err)
      } else {
          runtime.GC()
          pprof.WriteHeapProfile(f)                                                                                                                                        
          f.Close()
      }
      if f, err := os.Create("heap_prof" + "." + timestamp); err != nil {
        log.Debug("heap profile failed:", err)
      } else {
        p := pprof.Lookup("heap")
        p.WriteTo(f, 2)
      }
   }

    func (t *Trainer) Start() {      
    .......
      if i%1000==0 {
        //if `len(pibns)` is not very large , record some meminfo
        lookupMem()
      }
    .......
Run Code Online (Sandbox Code Playgroud)