gpt4 book ai didi

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

转载 作者:IT王子 更新时间:2023-10-29 00:47:35 27 4
gpt4 key购买 nike

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

目前我的应用程序很快达到 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/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com