memory-leaks - Go:内存使用过多,内存泄漏

标签 memory-leaks go garbage-collection

我非常非常注意内存,因为我必须编写需要处理大量数据集的程序。

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

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

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

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

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

绝对的噩梦! Go 有什么问题?有什么办法可以解决这个问题吗?

package main

import (
    "bytes"
    "code.google.com/p/go.text/transform"
    "code.google.com/p/go.text/unicode/norm"
    "compress/zlib"
    "encoding/gob"
    "fmt"
    "github.com/AlasdairF/BinSearch"
    "io/ioutil"
    "os"
    "regexp"
    "runtime"
    "strings"
    "unicode"
    "unicode/utf8"
)

type TokensStruct struct {
    binsearch.Key_string
    Value [][5]uint32
}

type Trainer struct {
    Tokens      TokensStruct
    TokensCount uint
}

func checkErr(err error) {
    if err == nil {
        return
    }
    fmt.Println(`Some Error:`, err)
    panic(err)
}

// Local helper function for normalization of UTF8 strings.
func isMn(r rune) bool {
    return unicode.Is(unicode.Mn, r) // Mn: nonspacing marks
}

// This map is used by RemoveAccents function to convert non-accented characters.
var transliterations = map[rune]string{'Æ': "E", 'Ð': "D", 'Ł': "L", 'Ø': "OE", 'Þ': "Th", 'ß': "ss", 'æ': "e", 'ð': "d", 'ł': "l", 'ø': "oe", 'þ': "th", 'Œ': "OE", 'œ': "oe"}

//  removeAccentsBytes converts accented UTF8 characters into their non-accented equivalents, from a []byte.
func removeAccentsBytesDashes(b []byte) ([]byte, error) {
    mnBuf := make([]byte, len(b))
    t := transform.Chain(norm.NFD, transform.RemoveFunc(isMn), norm.NFC)
    n, _, err := t.Transform(mnBuf, b, true)
    if err != nil {
        return nil, err
    }
    mnBuf = mnBuf[:n]
    tlBuf := bytes.NewBuffer(make([]byte, 0, len(mnBuf)*2))
    for i, w := 0, 0; i < len(mnBuf); i += w {
        r, width := utf8.DecodeRune(mnBuf[i:])
        if r == '-' {
            tlBuf.WriteByte(' ')
        } else {
            if d, ok := transliterations[r]; ok {
                tlBuf.WriteString(d)
            } else {
                tlBuf.WriteRune(r)
            }
        }
        w = width
    }
    return tlBuf.Bytes(), nil
}

func LoadZip(filename string) ([]byte, error) {
    // Open file for reading
    fi, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer fi.Close()
    // Attach ZIP reader
    fz, err := zlib.NewReader(fi)
    if err != nil {
        return nil, err
    }
    defer fz.Close()
    // Pull
    data, err := ioutil.ReadAll(fz)
    if err != nil {
        return nil, err
    }
    return norm.NFC.Bytes(data), nil // return normalized
}

func getTokens(pibn string) []string {
    var data []byte
    var err error
    data, err = LoadZip(`/storedir/` + pibn + `/text.zip`)
    checkErr(err)
    data, err = removeAccentsBytesDashes(data)
    checkErr(err)
    data = bytes.ToLower(data)
    data = reg2.ReplaceAll(data, []byte("$2")) // remove contractions
    data = reg.ReplaceAllLiteral(data, nil)
    tokens := strings.Fields(string(data))
    return tokens
}

func (t *Trainer) Start() {
    data, err := ioutil.ReadFile(`list.txt`)
    checkErr(err)
    pibns := bytes.Fields(data)
    for i, pibn := range pibns {
        tokens := getTokens(string(pibn))
        t.addTokens(tokens)
        if i%100 == 0 {
            runtime.GC() // I added this just to try to stop the memory craziness, but it makes no difference
        }
    }
}

func (t *Trainer) addTokens(tokens []string) {
    for _, tok := range tokens {
        if _, ok := dictionary[tok]; ok {
            if indx, ok2 := t.Tokens.Find(tok); ok2 {
                ar := t.Tokens.Value[indx]
                ar[0]++
                t.Tokens.Value[indx] = ar
                t.TokensCount++
            } else {
                t.Tokens.AddKeyAt(tok, indx)
                t.Tokens.Value = append(t.Tokens.Value, [5]uint32{0, 0, 0, 0, 0})
                copy(t.Tokens.Value[indx+1:], t.Tokens.Value[indx:])
                t.Tokens.Value[indx] = [5]uint32{1, 0, 0, 0, 0}
                t.TokensCount++
            }
        }
    }
    return
}

func LoadDictionary() {
    dictionary = make(map[string]bool)
    data, err := ioutil.ReadFile(`dictionary`)
    checkErr(err)
    words := bytes.Fields(data)
    for _, word := range words {
        strword := string(word)
        dictionary[strword] = false
    }
}

var reg = regexp.MustCompile(`[^a-z0-9\s]`)
var 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
var dictionary map[string]bool

func main() {
    trainer := new(Trainer)
    LoadDictionary()
    trainer.Start()
}

最佳答案

如果您从一个大字符串中进行分词,请确保避免内存固定。从上面的评论来看,这些标记听起来像是一个大字符串的子字符串。

您可能需要在 getTokens() 函数中添加一些额外的内容,以确保 token 不会固定内存。

func getTokens(...) {
    // near the end of your program
    for i, t := range(tokens) {
        tokens[i] = string([]byte(t))
    }
}

顺便说一下,使用 ioutil.ReadFile 将整个文件读入内存一下子看起来很可疑。你确定你不能使用 bufio.Scanner

我正在更仔细地查看代码...如果您真的关心内存问题,请利用 io.Reader .您应该尽量避免一次吸收整个文件的内容。使用 io.Reader 和 transform “沿着 Cereal ”。您现在使用它的方式与其初衷背道而驰。您正在使用的转换包的全部意义在于构建可以流式传输数据的灵活读取器。

例如,这是您正在做的事情的简化:

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "unicode/utf8"

    "code.google.com/p/go.text/transform"
)

type AccentsTransformer map[rune]string

func (a AccentsTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
    for nSrc < len(src) {
        // If we're at the edge, note this and return.
        if !atEOF && !utf8.FullRune(src[nSrc:]) {
            err = transform.ErrShortSrc
            return
        }
        r, width := utf8.DecodeRune(src[nSrc:])
        if r == utf8.RuneError && width == 1 {
            err = fmt.Errorf("Decoding error")
            return
        }
        if d, ok := a[r]; ok {
            if nDst+len(d) > len(dst) {
                err = transform.ErrShortDst
                return
            }
            copy(dst[nDst:], d)
            nSrc += width
            nDst += len(d)
            continue
        }

        if nDst+width > len(dst) {
            err = transform.ErrShortDst
            return
        }
        copy(dst[nDst:], src[nSrc:nSrc+width])
        nDst += width
        nSrc += width
    }
    return
}

func main() {
    transliterations := AccentsTransformer{'Æ': "E", 'Ø': "OE"}
    testString := "cØØl beÆns"
    b := transform.NewReader(bytes.NewBufferString(testString), transliterations)
    scanner := bufio.NewScanner(b)
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() {
        fmt.Println("token:", scanner.Text())
    }
}

然后将变压器链接在一起变得非常容易。因此,例如,如果我们想从输入流中删除所有连字符,只需使用 transform.Chain 即可。适本地:

func main() {
    transliterations := AccentsTransformer{'Æ': "E", 'Ø': "OE"}
    removeHyphens := transform.RemoveFunc(func(r rune) bool {
        return r == '-'
    })
    allTransforms := transform.Chain(transliterations, removeHyphens)

    testString := "cØØl beÆns - the next generation"
    b := transform.NewReader(bytes.NewBufferString(testString), allTransforms)
    scanner := bufio.NewScanner(b)
    scanner.Split(bufio.ScanWords)
    for scanner.Scan() {
        fmt.Println("token:", scanner.Text())
    }
}

我没有详尽地测试上面的代码,所以请不要在没有充分测试的情况下复制粘贴它。 :P 我只是很快就把它煮熟了。但是这种方法——避免读取整个文件——会更好地扩展,因为它将以 block 的形式读取文件。

关于memory-leaks - Go:内存使用过多,内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25175111/

相关文章:

c# - 如何在 Visual Studio 中读取内存快照

windows-vista - Vista/Win2008 上的临界区会泄漏内存吗?

go - 无法使用Paho库连接到mosquitto 2.0

json - 使用 Golang json.NewDecoder/json.NewEncoder

python-3.x - 创建实例失败时调用的析构函数?

javascript - Node JS forEach 内存泄漏问题

iOS Swift - 调试内存泄漏

go - 是否可以使用 go :generate 重定向标准输入/标准输出

java - 如果垃圾收集器没有删除未引用的对象,它们还能运行吗?

java - 一个艰难的 java 垃圾收集转向我,我真的陷入其中