我非常非常记忆,因为我必须编写需要处理大量数据集的程序。
目前我的应用程序很快就会达到32GB内存,开始交换,然后被系统杀死。
我不明白这是怎么回事,因为除了TokensStruct
结构中的TokensCount
和Trainer
之外,所有变量都是可收集的(在函数中并且快速发布)。 TokensCount
只是一个小问题。 TokensStruct
是[5] uint32和字符串的1,000,000行切片,因此这意味着20个字节+字符串,我们可以为每个记录调用最多50个字节。 50 * 1000000 =需要50MB内存。因此,这个脚本不应该在函数中使用超过50MB +开销+临时可收集变量(可能另外50MB最大)。TokensStruct
的最大可能大小为5,000,000,因为这是{{1}的大小但即使这样,它只有250MB的内存。 dictionary
是一张地图,显然使用了大约600MB的内存,因为这就是应用程序启动的方式,但这不是问题,因为dictionary
只加载一次而且永远不会再写入。
相反,它使用32GB内存然后死掉。通过它的速度,我希望如果可能的话,它会愉快地获得1TB的内存。内存似乎以加载文件大小的线性方式增加,这意味着它似乎永远不会清除任何内存。进入应用程序的所有内容都分配了更多的内存,永远不会释放内存。
我尝试实施dictionary
,以防垃圾收集活动不够频繁,但这没有任何区别。
由于内存使用量以线性方式增加,因此这意味着runtime.GC()
或GetTokens()
中存在内存泄漏。我不知道这是怎么回事,因为它们都是功能,只做一个任务然后关闭。或者可能是LoadZip()
中的tokens
变量是泄漏的原因。基本上看起来每个加载和解析的文件都不会从内存中释放出来,因为这是内存以线性方式填充并保持上升到32GB ++的唯一方式。
绝对的噩梦! Go有什么问题?有什么方法可以解决这个问题吗?
Start()
答案 0 :(得分:1)
如果您要从大字符串中进行标记,请确保避免内存固定。从上面的评论中,听起来像令牌是一个大字符串的子串。
您可能需要在getTokens()函数中添加一些额外内容,以确保令牌不会固定内存。
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“沿着谷物”。你现在使用它的方式是违背其意图的。您正在使用的转换包的重点是构建可以流式传输数据的灵活读者。
例如,这里简化了您正在做的事情:
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我刚刚把它煮熟了。但是这种方法 - 避免整个文件读取 - 会更好地扩展,因为它会以块的形式读取文件。
答案 1 :(得分:0)
1“list.txt”和“词典”有多大?如果它太大了,难怪记忆力如此之大
pibns := bytes.Fields(data)
len(pibns)
多少钱?
2启动gc debug(执行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()
}
.......