搜索
首页后端开发Golang从大对象中释放内存

从大对象中释放内存

Feb 09, 2024 am 09:03 AM
内存占用

从大对象中释放内存

php小编柚子为您介绍一种优化内存使用的技巧——从大对象中释放内存。在开发过程中,我们经常会创建一些大对象,比如大数组或大型数据库查询结果,这些对象会占用大量内存资源。当我们使用完这些对象后,及时释放内存是一种良好的编程习惯。本文将向您展示如何从大对象中释放内存,以提高应用程序的性能和效率。

问题内容

我遇到了一些我不明白的事情。希望大家帮忙!

资源:

  1. https://medium.com/@chaewonkong/solving-memory-leak-issues-in-go-http-clients-ba0b04574a83
  2. https://www.golinuxcloud.com/golang-garbage-collector/

我在几篇文章中读到建议,在我们不再需要它们之后,我们可以通过将大切片和映射(我想这适用于所有引用类型)设置为 nil 来简化 gc 的工作。这是我读过的示例之一:

func ProcessResponse(resp *http.Response) error {
    data, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return err
    }
    // Process data here

    data = nil // Release memory
    return nil
}

据我了解,当函数 processresponse 完成时,data 变量将超出范围,基本上将不再存在。然后,gc 将验证是否没有对 []byte 切片(data 指向的切片)的引用,并将清除内存。

data 设置为 data 设置为 nil 如何改进垃圾收集?

谢谢!

解决方法

正如其他人已经指出的那样:在返回之前设置 data = nil 不会改变 gc 方面的任何内容。 go 编译器将应用优化,并且 golang 的垃圾收集器在不同的阶段工作。用最简单的术语(有许多遗漏和过度简化):设置 data = nil ,并删除对底层切片的所有引用不会触发不再引用的内存的原子样式释放。一旦切片不再被引用,它就会被标记为这样,并且关联的内存直到下一次扫描才会被释放。

垃圾收集是一个难题,很大程度上是因为它不是那种具有能为所有用例产生最佳结果的最佳解决方案的问题。多年来,go 运行时已经发展了很多,重要的工作正是在运行时垃圾收集器上完成的。结果是,在极少数情况下,简单的 somevar = nil 会产生哪怕很小的差异,更不用说明显的差异了。

如果您正在寻找一些简单的经验法则类型提示,这些提示可能会影响与垃圾收集(或一般的运行时内存管理)相关的运行时开销,我确实知道这句话似乎模糊地涵盖了一个在你的问题中:

建议我们可以通过设置大切片和映射来简化 gc 的工作

在分析代码时,这可以产生显着的结果。假设您正在读取需要处理的大量数据,或者您必须执行某种其他类型的批处理操作并返回切片,那么人们编写这样的内容并不罕见:

func processstuff(input []sometypes) []resulttypes {
    data := []resulttypes{}
    for _, in := range input {
        data = append(data, processt(in))
    }
    return data
}

通过将代码更改为以下内容可以很容易地优化:

func processstuff(input []sometypes) []resulttypes {
    data := make([]resulttypes, 0, len(input)) // set cap
    for _, in := range input {
        data = append(data, processt(in))
    }
    return data
}

第一个实现中发生的情况是,您使用 lencap 为 0 创建一个切片。第一次调用 append 时,您超出了切片的当前容量,这将导致运行时分配内存。正如此处所解释的,新容量的计算相当简单,内存被分配,数据被分配复制过来:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)

本质上,每次当要附加的切片已满时(即 len == cap)调用 append 时,您将分配一个可容纳: (len + 1) * 2 元素的新切片。知道在第一个示例中 datalencap == 0 开头,让我们看看这意味着什么:

1st iteration: append creates slice with cap (0+1) *2, data is now len 1, cap 2
2nd iteration: append adds to data, now has len 2, cap 2
3rd iteration: append allocates a new slice with cap (2 + 1) *2, copies the 2 elements from data to this slice and adds the third, data is now reassigned to a slice with len 3, cap 6
4th-6th iterations: data grows to len 6, cap 6
7th iteration: same as 3rd iteration, although cap is (6 + 1) * 2, everything is copied over, data is reassigned a slice with len 7, cap 14

如果切片中的数据结构较大(即许多嵌套结构、大量间接寻址等),那么这种频繁的重新分配和复制可能会变得相当昂贵。如果您的代码包含大量此类循环,它将开始显示在 pprof 中(您将开始看到花费大量时间调用 gcmalloc)。此外,如果您正在处理 15 个输入值,您的数据切片最终将如下所示:

dataslice {
    len: 15
    cap: 30
    data underlying_array[30]
}

这意味着您将为 30 个值分配内存,而您只需要 15 个值,并且您将将该内存分配为 4 个逐渐增大的块,并在每次重新分配时复制数据。

相比之下,第二个实现将在循环之前分配一个如下所示的数据片:

data {
    len: 0
    cap: 15
    data underlying_array[15]
}

它是一次性分配的,因此不需要重新分配和复制,并且返回的切片将占用一半的内存空间。从这个意义上说,我们首先在开始时分配更大的内存块,以减少稍后所需的增量分配和复制调用的数量,这总体上会降低运行时成本。

如果我不知道需要多少内存怎么办

这是一个公平的问题。这个例子并不总是适用。在这种情况下,我们知道需要多少个元素,并且可以相应地分配内存。有时,世界并不是这样运作的。如果您不知道最终需要多少数据,那么您可以:

  1. 做出有根据的猜测:gc 很困难,而且与您不同的是,编译器和 go 运行时缺乏模糊逻辑,人们必须提出现实、合理的猜测。有时它会像这样简单:“嗯,我从该数据源获取数据,我们只存储最后 n 个元素,所以最坏的情况下,我将处理 n 个元素” em>,有时它有点模糊,例如:您正在处理包含 sku、产品名称和库存数量的 csv。您知道 sku 的长度,可以假设库存数量为 1 到 5 位数字之间的整数,产品名称平均为 2-3 个单词长。英文单词的平均长度为 6 个字符,因此您可以粗略地了解 csv 行由多少字节组成:假设 sku == 10 个字符,80 个字节,产品描述 2.5 * 6 * 8 = 120 个字节,以及 ~ 4 个字节表示库存计数 + 2 个逗号和一个换行符,平均预期行长度为 207 个字节,为了谨慎起见,我们将其称为 200。统计输入文件,将其大小(以字节为单位)除以 200,您应该对行数有一个可用的、稍微保守的估计。在该代码末尾添加一些日志记录,比较上限与估计值,然后您可以相应地调整您的预测计算。
  2. 分析您的代码。有时,您会发现自己正在开发新功能或全新项目,而您没有历史数据可以依靠进行猜测。在这种情况下,您可以简单地猜测,运行一些测试场景,或者启动一个测试环境来提供您的代码生产数据版本并分析代码。当您正在主动分析一两个切片/映射的内存使用/运行时成本时,我必须强调这是优化。仅当这是瓶颈或明显问题时(例如,运行时内存分配阻碍了整体分析),您才应该在这方面花费时间。在绝大多数情况下,这种级别的优化将牢牢地属于微优化的范畴。 坚持80-20原则

回顾

不,将一个简单的切片变量设置为 nil 在 99% 的情况下不会产生太大影响。创建和附加到地图/切片时,更可能产生影响的是通过使用 make() + 指定合理的 cap 值来减少无关分配。其他可以产生影响的事情是使用指针类型/接收器,尽管这是一个需要深入研究的更复杂的主题。现在,我只想说,我一直在开发一个代码库,该代码库必须对远远超出典型 uint64 范围的数字进行操作,不幸的是,我们必须能够以更精确的方式使用小数比 float64 将允许。我们通过使用像 holiman/uint256 这样的东西解决了 uint64 问题,它使用指针接收器,并解决shopspring/decimal 的十进制问题,它使用值接收器并复制所有内容。在花费大量时间优化代码之后,我们已经达到了使用小数时不断复制值的性能影响已成为问题的地步。看看这些包如何实现加法等简单操作,并尝试找出哪个操作成本更高:

// original
a, b := 1, 2
a += b
// uint256 version
a, b := uint256.NewUint(1), uint256.NewUint(2)
a.Add(a, b)
// decimal version
a, b := decimal.NewFromInt(1), decimal.NewFromInt(2)
a = a.Add(b)

这些只是我在最近的工作中花时间优化的几件事,但从中得到的最重要的一点是:

过早的优化是万恶之源

当您处理更复杂的问题/代码时,您需要花费大量精力来研究切片或映射的分配周期,因为潜在的瓶颈和优化需要付出很大的努力。您可以而且可以说应该采取措施避免过于浪费(例如,如果您知道所述切片的最终长度是多少,则设置切片上限),但您不应该浪费太多时间手工制作每一行,直到该代码的内存占用尽可能小。成本将是:代码更脆弱/更难以维护和阅读,整体性能可能会恶化(说真的,你可以相信 go 运行时会做得很好),大量的血、汗和泪水,以及急剧下降在生产力方面。

以上是从大对象中释放内存的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:stackoverflow。如有侵权,请联系admin@php.cn删除
Golang和Python:了解差异Golang和Python:了解差异Apr 18, 2025 am 12:21 AM

Golang和Python的主要区别在于并发模型、类型系统、性能和执行速度。1.Golang使用CSP模型,适用于高并发任务;Python依赖多线程和GIL,适合I/O密集型任务。2.Golang是静态类型,Python是动态类型。3.Golang编译型语言执行速度快,Python解释型语言开发速度快。

Golang vs.C:评估速度差Golang vs.C:评估速度差Apr 18, 2025 am 12:20 AM

Golang通常比C 慢,但Golang在并发编程和开发效率上更具优势:1)Golang的垃圾回收和并发模型使其在高并发场景下表现出色;2)C 通过手动内存管理和硬件优化获得更高性能,但开发复杂度较高。

Golang:云计算和DevOps的关键语言Golang:云计算和DevOps的关键语言Apr 18, 2025 am 12:18 AM

Golang在云计算和DevOps中的应用广泛,其优势在于简单性、高效性和并发编程能力。1)在云计算中,Golang通过goroutine和channel机制高效处理并发请求。2)在DevOps中,Golang的快速编译和跨平台特性使其成为自动化工具的首选。

Golang和C:了解执行效率Golang和C:了解执行效率Apr 18, 2025 am 12:16 AM

Golang和C 在执行效率上的表现各有优势。1)Golang通过goroutine和垃圾回收提高效率,但可能引入暂停时间。2)C 通过手动内存管理和优化实现高性能,但开发者需处理内存泄漏等问题。选择时需考虑项目需求和团队技术栈。

Golang vs. Python:并发和多线程Golang vs. Python:并发和多线程Apr 17, 2025 am 12:20 AM

Golang更适合高并发任务,而Python在灵活性上更有优势。1.Golang通过goroutine和channel高效处理并发。2.Python依赖threading和asyncio,受GIL影响,但提供多种并发方式。选择应基于具体需求。

Golang和C:性能的权衡Golang和C:性能的权衡Apr 17, 2025 am 12:18 AM

Golang和C 在性能上的差异主要体现在内存管理、编译优化和运行时效率等方面。1)Golang的垃圾回收机制方便但可能影响性能,2)C 的手动内存管理和编译器优化在递归计算中表现更为高效。

Golang vs. Python:申请和用例Golang vs. Python:申请和用例Apr 17, 2025 am 12:17 AM

selectgolangforhighpperformanceandcorrency,ifealforBackendServicesSandNetwork程序; selectpypypythonforrapiddevelopment,dataScience和machinelearningDuetoitsverserverserverserversator versator anderticality andextility andextentensivelibraries。

Golang vs. Python:主要差异和相似之处Golang vs. Python:主要差异和相似之处Apr 17, 2025 am 12:15 AM

Golang和Python各有优势:Golang适合高性能和并发编程,Python适用于数据科学和Web开发。 Golang以其并发模型和高效性能着称,Python则以简洁语法和丰富库生态系统着称。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
威尔R.E.P.O.有交叉游戏吗?
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。