求教一个内存溢出的问题 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
}
1
Maboroshii 2022-11-29 21:09:45 +08:00
怎么复现的?
|
2
sealinfree OP @Maboroshii #1 线上系统 pprof 比较了 6 个小时间隔的内存数据,这部分增长了 3G 的内存
|
3
yin1999 2022-11-29 21:20:05 +08:00 1
ReadAll 里面应该返回 string ,这不会增加开销,用指针反而会增加 GC 的开销。用 copy 拷贝数组,应该不会拷贝字符串吧,strings.Split 函数返回的子串都是对原字符串的引用。尝试在向里面的匿名函数传递数组元素时,使用 strings.Clone() 拷贝一份子串
|
4
yin1999 2022-11-29 21:21:51 +08:00 1
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 } |
5
sealinfree OP @yin1999 #3 感谢指点,我改下试试
|
6
yin1999 2022-11-29 21:25:09 +08:00 2
也可以选择在
data1 = infoArr[0] data2 = infoArr[1] 这里克隆两个字符串 data1 = strings.Clone(infoArr[0]) data2 = strings.Clone(infoArr[1]) 应该是 GMap 长期持有子串,造成整个字符串无法被 GC ,尝试在长期持有子串的地方克隆一下子串 |
7
sealinfree OP @yin1999 #6 有道理!感谢!受教了,我改好上线跑一段时间看看
|
8
sealinfree OP @yin1999 问题解决,内存稳定了,感谢
|
9
swulling 2022-11-29 22:30:30 +08:00 via iPhone
嗯,这应该是经典的子串内存泄漏问题。
字符串的子串不回收会导致整个字符串不回收。 |
10
rrfeng 2022-11-29 22:58:12 +08:00 via Android
问题解决了,那只好吐槽一下这段代码了…
1. 一次读完整个文件有内存爆炸的风险,建议使用 bufio 逐行处理 2. 每行放到一个 goroutine 里切分还要加锁,真不如顺序处理快……这样写又复杂又慢又容易错 |
11
sealinfree OP @rrfeng 谢谢指导,因为数据最终要放到内存作为常驻缓存,所以逐行读取和一次性读取区别不大;切分处理速度比顺序处理快 6 倍,配置是 12 核心 24 线程虚拟机,也是优化过才成了这个样子,第一版是流式顺序处理,载入一次 1G 文件要 1 分钟多,无法忍受,改为多线程变成 10-16 秒了
|
12
sealinfree OP @swulling 是的,之前不明白此处该使用深拷贝,这次学习了
|
13
rrfeng 2022-11-30 06:34:44 +08:00 via Android
|
14
lysS 2022-11-30 10:29:32 +08:00
楼上怎么一唱一和的。。。
有几点: 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) |
15
sealinfree OP @lysS 1 、截取了片段代码,部分是为了表达清楚逻辑构造的。
2 、最开始返回的 str ,但是内存溢出,就不断尝试调整 3 、实际使用的是带分区读写锁的 map ,还是为了逻辑简单直接构造了一个 map 4 、服务器内存比较大,直接读取实测速度更快,就改为这个版本了 5 、该函数底层就是 path.Join ,只是因为路径比较复杂,要动态获取,单独封装了一个函数 6 、指针引用再搭配分区 map ,会产生一些不可预料的修改错误,所以还是用值传递比较多了 最后,使用 string()转换不能解决溢出问题,使用 strings.clone 深拷贝才可以 |
16
sealinfree OP @lysS 感谢这么多较真的朋友!
|
17
lysS 2022-12-01 09:42:31 +08:00
@sealinfree string([]byte) 就是重新分配的内存,无论是 header 还是数据本身;和之前不存在任何引用关系
|
18
sealinfree OP @lysS 好的,那回头我再测试下
|
19
macscsbf 2022-12-01 13:13:13 +08:00
我想知道是不是因为你用 map 存储着 content 的引用导致了这个 content 一直没被释放掉
|
20
darknoll 2022-12-01 20:01:39 +08:00
var GMap=map[string]string
这句能编译过? |
21
sealinfree OP @darknoll 更正 var GMap=map[string]string{}
|
22
sealinfree OP @macscsbf 存的都是值,没有使用指针
|
23
macscsbf 2022-12-02 08:45:56 +08:00
@sealinfree data1 和 data2 都是 content 的子串,本质就是指针指向了 content
|
24
sealinfree OP @macscsbf 哦,此处应该用 strings.clone 复制一份
|