Golang Go语言中求教 os.readfile 内存溢出的问题
求教一个内存溢出的问题 pprof 显示在 os.readfile 上有大量内存占用无法释放 请问我下面的写法具体哪里有问题 已经尝试很多办法,都没效果 恳请赐教
var GMap=map[string]string
func DomainMapLoadFromFile() {
GMap=make(map[string]string,10)
//尝试解决内存溢出
fileStr := ReadAll(“DataMap”, “CacheMap”)
contentStrArr := strings.Split(*fileStr, “\n”)
contentLen := len(contentStrArr)
contentArr := make([]string, contentLen)
copy(contentArr, contentStrArr)
var wg sync.WaitGroup
wg.Add(contentLen)
for i := range contentArr {
go func(content string) {
infoArr := strings.Split(content, “|”)
var deviceId int64
l := len(infoArr)
if l == 2 {
data1 = infoArr[0]
data2 = infoArr[1]
} else {
fmt.Println(“不符合长度 5-6 的数据,content:” + content)
wg.Done()
return
}
GMap[data1]=data2
wg.Done()
}(contentArr[i])
}
wg.Wait()
}
func ReadAll(fileName, dirName string) *string {
content, _ := os.ReadFile(GetFilePathPWD(fileName, dirName))
contentStr := string(content)
return &contentStr
}
Golang Go语言中求教 os.readfile 内存溢出的问题
更多关于Golang Go语言中求教 os.readfile 内存溢出的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
怎么复现的?
更多关于Golang Go语言中求教 os.readfile 内存溢出的问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
#1 线上系统 pprof 比较了 6 个小时间隔的内存数据,这部分增长了 3G 的内存
ReadAll 里面应该返回 string ,这不会增加开销,用指针反而会增加 GC 的开销。用 copy 拷贝数组,应该不会拷贝字符串吧,strings.Split 函数返回的子串都是对原字符串的引用。尝试在向里面的匿名函数传递数组元素时,使用 strings.Clone() 拷贝一份子串
var GMap=map[string]string
func DomainMapLoadFromFile() {
GMap=make(map[string]string,10)
//尝试解决内存溢出
fileStr := ReadAll(“DataMap”, “CacheMap”)
contentStrArr := strings.Split(*fileStr, “\n”)
var wg sync.WaitGroup
wg.Add(contentLen)
for i := range contentStrArr {
go func(content string) {
infoArr := strings.Split(content, “|”)
var deviceId int64
l := len(infoArr)
if l == 2 {
data1 = infoArr[0]
data2 = infoArr[1]
} else {
fmt.Println(“不符合长度 5-6 的数据,content:” + content)
wg.Done()
return
}
GMap[data1]=data2
wg.Done()
}(strings.Clone(contentStrArr[i]))
}
wg.Wait()
}
func ReadAll(fileName, dirName string) string {
content, _ := os.ReadFile(GetFilePathPWD(fileName, dirName))
contentStr := string(content)
return contentStr
}
#3 感谢指点,我改下试试
也可以选择在
data1 = infoArr[0]
data2 = infoArr[1]
这里克隆两个字符串
data1 = strings.Clone(infoArr[0])
data2 = strings.Clone(infoArr[1])
应该是 GMap 长期持有子串,造成整个字符串无法被 GC ,尝试在长期持有子串的地方克隆一下子串
#6 有道理!感谢!受教了,我改好上线跑一段时间看看
问题解决,内存稳定了,感谢
嗯,这应该是经典的子串内存泄漏问题。
字符串的子串不回收会导致整个字符串不回收。
问题解决了,那只好吐槽一下这段代码了…
1. 一次读完整个文件有内存爆炸的风险,建议使用 bufio 逐行处理
2. 每行放到一个 goroutine 里切分还要加锁,真不如顺序处理快……这样写又复杂又慢又容易错
谢谢指导,因为数据最终要放到内存作为常驻缓存,所以逐行读取和一次性读取区别不大;切分处理速度比顺序处理快 6 倍,配置是 12 核心 24 线程虚拟机,也是优化过才成了这个样子,第一版是流式顺序处理,载入一次 1G 文件要 1 分钟多,无法忍受,改为多线程变成 10-16 秒了
是的,之前不明白此处该使用深拷贝,这次学习了
一次读取你要用掉 2 倍内存
处理速度这个我不太相信,要不把顺序处理的代码也贴一下,还有文件内容
楼上怎么一唱一和的。。。
有几点:
1. 代码有多处基础的语法错误
2. ReadAll 为什么要返回 str ptr ?
3. map 并发操作不安全
4. 你这个需求可以流式处理,不需要首先就 load 所有数据到内存
5. GetFilePathPWD 可以 path.Join ,当然可能你有特殊的需求
6. 值拷贝通常比指针引用占用更多的内存。
当然你这确实可能存在溢出的问题,大概是这样:一个很大的字符串 str ,只把它的一部分放进 map[1]=str[0:3]后,导致这个大的 str 的其他部分不能被回收。我不太清楚 gogc 对这种情况是咋做的。
如果上面假设成立,解决办法也很简单,用[]byte 从文件读,存入 map 的时候 map[string(data1)] = string(data2)
1 、截取了片段代码,部分是为了表达清楚逻辑构造的。
2 、最开始返回的 str ,但是内存溢出,就不断尝试调整
3 、实际使用的是带分区读写锁的 map ,还是为了逻辑简单直接构造了一个 map
4 、服务器内存比较大,直接读取实测速度更快,就改为这个版本了
5 、该函数底层就是 path.Join ,只是因为路径比较复杂,要动态获取,单独封装了一个函数
6 、指针引用再搭配分区 map ,会产生一些不可预料的修改错误,所以还是用值传递比较多了
最后,使用 string()转换不能解决溢出问题,使用 strings.clone 深拷贝才可以
感谢这么多较真的朋友!
string([]byte) 就是重新分配的内存,无论是 header 还是数据本身;和之前不存在任何引用关系
好的,那回头我再测试下
我想知道是不是因为你用 map 存储着 content 的引用导致了这个 content 一直没被释放掉
var GMap=map[string]string
这句能编译过?
更正 var GMap=map[string]string{}
存的都是值,没有使用指针
data1 和 data2 都是 content 的子串,本质就是指针指向了 content
哦,此处应该用 strings.clone 复制一份