The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
sealinfree

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

  •  
  •   sealinfree · Nov 29, 2022 · 2412 views
    This topic created in 1295 days ago, the information mentioned may be changed or developed.

    求教一个内存溢出的问题 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
    }
    
    Supplement 1  ·  Nov 29, 2022
    问题已解决,使用 3 楼提供的字符串深拷贝 strings.clone 解决了,感谢!
    解决该类型问题的关键字,deepcopy ,有需求的朋友,可以 github 一下,如果需要结构体等复杂类型的深拷贝,可以使用 github.com/antlabs/deepcopy ,测试了三个 go 的深拷贝包,这个最快
    24 replies    2022-12-02 15:21:26 +08:00
    Maboroshii
        1
    Maboroshii  
       Nov 29, 2022
    怎么复现的?
    sealinfree
        2
    sealinfree  
    OP
       Nov 29, 2022
    @Maboroshii #1 线上系统 pprof 比较了 6 个小时间隔的内存数据,这部分增长了 3G 的内存
    yin1999
        3
    yin1999  
       Nov 29, 2022   ❤️ 1
    ReadAll 里面应该返回 string ,这不会增加开销,用指针反而会增加 GC 的开销。用 copy 拷贝数组,应该不会拷贝字符串吧,strings.Split 函数返回的子串都是对原字符串的引用。尝试在向里面的匿名函数传递数组元素时,使用 strings.Clone() 拷贝一份子串
    yin1999
        4
    yin1999  
       Nov 29, 2022   ❤️ 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
    }
    sealinfree
        5
    sealinfree  
    OP
       Nov 29, 2022
    @yin1999 #3 感谢指点,我改下试试
    yin1999
        6
    yin1999  
       Nov 29, 2022   ❤️ 2
    也可以选择在
    data1 = infoArr[0]
    data2 = infoArr[1]
    这里克隆两个字符串
    data1 = strings.Clone(infoArr[0])
    data2 = strings.Clone(infoArr[1])
    应该是 GMap 长期持有子串,造成整个字符串无法被 GC ,尝试在长期持有子串的地方克隆一下子串
    sealinfree
        7
    sealinfree  
    OP
       Nov 29, 2022
    @yin1999 #6 有道理!感谢!受教了,我改好上线跑一段时间看看
    sealinfree
        8
    sealinfree  
    OP
       Nov 29, 2022
    @yin1999 问题解决,内存稳定了,感谢
    swulling
        9
    swulling  
       Nov 29, 2022 via iPhone
    嗯,这应该是经典的子串内存泄漏问题。

    字符串的子串不回收会导致整个字符串不回收。
    rrfeng
        10
    rrfeng  
       Nov 29, 2022 via Android
    问题解决了,那只好吐槽一下这段代码了…
    1. 一次读完整个文件有内存爆炸的风险,建议使用 bufio 逐行处理
    2. 每行放到一个 goroutine 里切分还要加锁,真不如顺序处理快……这样写又复杂又慢又容易错
    sealinfree
        11
    sealinfree  
    OP
       Nov 30, 2022
    @rrfeng 谢谢指导,因为数据最终要放到内存作为常驻缓存,所以逐行读取和一次性读取区别不大;切分处理速度比顺序处理快 6 倍,配置是 12 核心 24 线程虚拟机,也是优化过才成了这个样子,第一版是流式顺序处理,载入一次 1G 文件要 1 分钟多,无法忍受,改为多线程变成 10-16 秒了
    sealinfree
        12
    sealinfree  
    OP
       Nov 30, 2022
    @swulling 是的,之前不明白此处该使用深拷贝,这次学习了
    rrfeng
        13
    rrfeng  
       Nov 30, 2022 via Android
    @sealinfree
    一次读取你要用掉 2 倍内存
    处理速度这个我不太相信,要不把顺序处理的代码也贴一下,还有文件内容
    lysS
        14
    lysS  
       Nov 30, 2022
    楼上怎么一唱一和的。。。

    有几点:
    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
        15
    sealinfree  
    OP
       Nov 30, 2022
    @lysS 1 、截取了片段代码,部分是为了表达清楚逻辑构造的。
    2 、最开始返回的 str ,但是内存溢出,就不断尝试调整
    3 、实际使用的是带分区读写锁的 map ,还是为了逻辑简单直接构造了一个 map
    4 、服务器内存比较大,直接读取实测速度更快,就改为这个版本了
    5 、该函数底层就是 path.Join ,只是因为路径比较复杂,要动态获取,单独封装了一个函数
    6 、指针引用再搭配分区 map ,会产生一些不可预料的修改错误,所以还是用值传递比较多了

    最后,使用 string()转换不能解决溢出问题,使用 strings.clone 深拷贝才可以
    sealinfree
        16
    sealinfree  
    OP
       Nov 30, 2022
    @lysS 感谢这么多较真的朋友!
    lysS
        17
    lysS  
       Dec 1, 2022
    @sealinfree string([]byte) 就是重新分配的内存,无论是 header 还是数据本身;和之前不存在任何引用关系
    sealinfree
        18
    sealinfree  
    OP
       Dec 1, 2022
    @lysS 好的,那回头我再测试下
    macscsbf
        19
    macscsbf  
       Dec 1, 2022
    我想知道是不是因为你用 map 存储着 content 的引用导致了这个 content 一直没被释放掉
    darknoll
        20
    darknoll  
       Dec 1, 2022
    var GMap=map[string]string
    这句能编译过?
    sealinfree
        21
    sealinfree  
    OP
       Dec 1, 2022
    @darknoll 更正 var GMap=map[string]string{}
    sealinfree
        22
    sealinfree  
    OP
       Dec 1, 2022
    @macscsbf 存的都是值,没有使用指针
    macscsbf
        23
    macscsbf  
       Dec 2, 2022
    @sealinfree data1 和 data2 都是 content 的子串,本质就是指针指向了 content
    sealinfree
        24
    sealinfree  
    OP
       Dec 2, 2022
    @macscsbf 哦,此处应该用 strings.clone 复制一份
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   5575 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 55ms · UTC 01:40 · PVG 09:40 · LAX 18:40 · JFK 21:40
    ♥ Do have faith in what you're doing.