我非常、非常注意内存,因为我必须编写需要处理大量数据集的程序。
当前我的应用程序很快达到32GB内存,开始交换,然后被系统杀死。
我不明白这是怎么可能的,因为除了Trainer
结构中的TokensStruct
和TokensCount
,所有变量都是可收集的(在函数中并快速释放)。TokensCount
只是一个单位。TokensStruct
是[5]uint32和string的1,000,000行切片,因此这意味着20字节+字符串,我们可以调用每条记录最多50字节。需要50*1000000 = 50MB内存。因此,该脚本在函数中使用的开销+临时可收集变量不应超过50MB(最多50MB)。TokensStruct
的最大潜在大小是5,000,000,因为这是dictionary
的大小,但即使这样,它也只有250MB的内存。dictionary
是一个地图,显然使用了大约600MB的内存,因为这就是应用程序启动的方式,但这不是一个问题,因为dictionary
只加载一次,永远不会再次写入。
相反,它使用32GB的内存,然后死亡。根据它的速度,我希望它能很高兴地达到1TB的内存。内存似乎随着加载文件的大小以线性方式增加,这意味着它似乎永远不会清除任何内存。所有进入应用程序的东西都会被分配更多的内存,并且内存永远不会被释放。
我尝试实现runtime.GC()
,以防垃圾收集不经常运行,但这没有什么不同。
由于内存使用以线性方式增加,因此这意味着GetTokens()
或LoadZip()
中存在内存泄漏。我不知道这是怎么回事,因为它们都是功能,只做一个任务,然后关闭。或者,Start()
中的tokens
变量可能是导致泄漏的原因。基本上,看起来加载和解析的每个文件都不会从内存中释放,因为这是内存以线性方式填充并继续上升到32GB++的唯一方法。
绝对的噩梦!围棋怎么了?有办法解决这个问题吗?
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-9s]`)
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()函数中添加一些额外的内容,以确保令牌不会固定内存。
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。
例如,下面是你正在做的事情的简化:
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的问题。适当链:
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 "list.txt"one_answers"dictionary"有多大?如果它那么大,难怪内存那么大
pibns := bytes.Fields(data)
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()
}
.......