求教 os.readfile 内存溢出的问题

2022-11-29 20:53:09 +08:00
 sealinfree

求教一个内存溢出的问题 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
}
1818 次点击
所在节点    Go 编程语言
24 条回复
Maboroshii
2022-11-29 21:09:45 +08:00
怎么复现的?
sealinfree
2022-11-29 21:15:00 +08:00
@Maboroshii #1 线上系统 pprof 比较了 6 个小时间隔的内存数据,这部分增长了 3G 的内存
yin1999
2022-11-29 21:20:05 +08:00
ReadAll 里面应该返回 string ,这不会增加开销,用指针反而会增加 GC 的开销。用 copy 拷贝数组,应该不会拷贝字符串吧,strings.Split 函数返回的子串都是对原字符串的引用。尝试在向里面的匿名函数传递数组元素时,使用 strings.Clone() 拷贝一份子串
yin1999
2022-11-29 21:21:51 +08:00
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
}
sealinfree
2022-11-29 21:23:48 +08:00
@yin1999 #3 感谢指点,我改下试试
yin1999
2022-11-29 21:25:09 +08:00
也可以选择在
data1 = infoArr[0]
data2 = infoArr[1]
这里克隆两个字符串
data1 = strings.Clone(infoArr[0])
data2 = strings.Clone(infoArr[1])
应该是 GMap 长期持有子串,造成整个字符串无法被 GC ,尝试在长期持有子串的地方克隆一下子串
sealinfree
2022-11-29 21:28:11 +08:00
@yin1999 #6 有道理!感谢!受教了,我改好上线跑一段时间看看
sealinfree
2022-11-29 22:22:09 +08:00
@yin1999 问题解决,内存稳定了,感谢
swulling
2022-11-29 22:30:30 +08:00
嗯,这应该是经典的子串内存泄漏问题。

字符串的子串不回收会导致整个字符串不回收。
rrfeng
2022-11-29 22:58:12 +08:00
问题解决了,那只好吐槽一下这段代码了…
1. 一次读完整个文件有内存爆炸的风险,建议使用 bufio 逐行处理
2. 每行放到一个 goroutine 里切分还要加锁,真不如顺序处理快……这样写又复杂又慢又容易错
sealinfree
2022-11-30 01:26:24 +08:00
@rrfeng 谢谢指导,因为数据最终要放到内存作为常驻缓存,所以逐行读取和一次性读取区别不大;切分处理速度比顺序处理快 6 倍,配置是 12 核心 24 线程虚拟机,也是优化过才成了这个样子,第一版是流式顺序处理,载入一次 1G 文件要 1 分钟多,无法忍受,改为多线程变成 10-16 秒了
sealinfree
2022-11-30 01:27:08 +08:00
@swulling 是的,之前不明白此处该使用深拷贝,这次学习了
rrfeng
2022-11-30 06:34:44 +08:00
@sealinfree
一次读取你要用掉 2 倍内存
处理速度这个我不太相信,要不把顺序处理的代码也贴一下,还有文件内容
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)
sealinfree
2022-11-30 23:13:27 +08:00
@lysS 1 、截取了片段代码,部分是为了表达清楚逻辑构造的。
2 、最开始返回的 str ,但是内存溢出,就不断尝试调整
3 、实际使用的是带分区读写锁的 map ,还是为了逻辑简单直接构造了一个 map
4 、服务器内存比较大,直接读取实测速度更快,就改为这个版本了
5 、该函数底层就是 path.Join ,只是因为路径比较复杂,要动态获取,单独封装了一个函数
6 、指针引用再搭配分区 map ,会产生一些不可预料的修改错误,所以还是用值传递比较多了

最后,使用 string()转换不能解决溢出问题,使用 strings.clone 深拷贝才可以
sealinfree
2022-11-30 23:14:21 +08:00
@lysS 感谢这么多较真的朋友!
lysS
2022-12-01 09:42:31 +08:00
@sealinfree string([]byte) 就是重新分配的内存,无论是 header 还是数据本身;和之前不存在任何引用关系
sealinfree
2022-12-01 10:05:16 +08:00
@lysS 好的,那回头我再测试下
macscsbf
2022-12-01 13:13:13 +08:00
我想知道是不是因为你用 map 存储着 content 的引用导致了这个 content 一直没被释放掉
darknoll
2022-12-01 20:01:39 +08:00
var GMap=map[string]string
这句能编译过?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/898889

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX