【问题标题】:Large memory usage slows down unrelated code大内存使用会减慢不相关的代码
【发布时间】:2019-05-27 07:34:03
【问题描述】:

我正在维护一个 Go 项目的代码,该项目读取和写入大量数据,并且已经成功完成了一段时间。最近,我做了一个更改:在程序开始时将一个包含大约 200 万条记录的 CSV 文件加载到一个带有结构值的映射中。此映射仅在 B 部分中使用,但首先在 A 部分中执行。第一部分的运行速度明显比以前慢(处理时间增加了四倍)。这很奇怪,因为那部分逻辑没有改变。 我花了一个星期试图解释这是怎么发生的。以下是我采取的步骤(提到性能时,我总是指A部分,其中不包括将数据加载到内存中的时间,实际上与它无关):

  • 程序在 Docker 容器内的服务器上运行。但是我已经能够在没有容器的情况下在我的笔记本电脑上重现它:与我在内存中没有加载文件数据的情况下运行它相比,性能确实有所下降。
  • 服务器有大量 RAM。尽管加载文件时显然会使用更多内存,但没有达到任何限制。我也没有看到内存使用和磁盘 I/O 出现峰值或其他奇怪的模式。对于这些检查,我使用了 pprof、htop 和 iotop。
  • 当加载数据但地图设置为零时,性能再次正常。
  • 将数据加载到切片而不是映射中可减少 x4 到 x2 的性能下降(但内存使用量与映射大致相同)。
  • 这让我想知道是否可以在 A 部分的某个地方访问地图/切片,即使它不应该访问。映射存储在结构类型的字段中。我检查了一下,这个结构总是通过指针传递(包括所有 goroutines)。将其设为全局变量而不是指针字段并不能解决问题。
  • 在标准库之外有一个依赖项。问题是图书馆引起的吗?它强制进行一些垃圾收集。禁用它并没有什么不同。我发现了另一个不相关的类似库,使用它作为替代可以提高性能,但加载文件数据时仍然需要更长的时间。

在这里,我绘制了内存中包含和不包含数据的指标:

什么可能导致这种影响或者我该如何发现它?

【问题讨论】:

  • 能否请您在图表的 X 和 Y 轴上添加标签
  • 两个厘米。首先,您的问题具体是什么?您想知道如何跟踪内存泄漏吗?其次,你没有展示一个最小的例子或任何代码。所以很难猜测发生了什么
  • The server had a huge amount of RAM. Although obviously more memory is used when the file is loaded, no limits are hit”。 CPU缓存大小限制如何?例如。加载数据可能会导致所有其他数据被推出缓存(并且由于缓存未命中而使使用其他数据的代码变慢)。
  • 用坐标轴上的标签和一个明确的问题更新了问题。
  • @Brendan 有趣,这是我没有想到的。但我不相信这是我遇到的。运行速度较慢的部分代码由文件读取和写入组成,不涉及任何先前的数据。

标签: performance go memory


【解决方案1】:

如果我做对了,你的流程看起来像这样:

  1. 从 CSV 读取 200 万行到 map -> struct
  2. 运行 A 部分(不需要来自 CSV 的数据)
  3. 使用 CSV 中的数据运行 B 部分

为什么要在需要之前读取数据,这是第一个问题,但这可能不是重点。

实际上,垃圾收集器可能会定期访问映射中的 200 万个结构。根据GOGC 的值,垃圾收集器的起搏器组件可能会随着分配的内存量的增加而更频繁地启动。因为这个映射是留给以后使用的,所以 GC 没有什么可做的,但无论如何它都会占用检查数据的周期。您可以做很多事情来验证并解释这种行为 - 所有这些事情都应该可以帮助您排除/确认垃圾收集是否会减慢您的速度。

  • 分析代码(显然,对诊断很重要)IIRC,CPU 配置文件更容易显示 GC 干预
  • 尝试禁用垃圾回收 (debug.SetGCPercent(-1))
  • 将地图存储在sync.Pool 中。这是一种为您设计的类型,用于保留您将手动管理的内容,并移出常规 GC 周期。
  • 仅在需要时读取 CSV,不要在 “part A”之前读取它
  • 流式传输文件,而不是在大量地图中读取它。 200万行,在内存中读取所有这些而不是逐行读取有什么价值?

【讨论】:

  • 为什么在我需要数据之前读取数据:如果它不是预期的格式,则快速失败(或警告)。
  • 我读取了内存中的 200 万条记录,因为它们不是我需要的顺序。到目前为止,我绕过这个问题的方法是读取记录,对它们进行排序并再次写入它们(使用 gob)。然后可以将它们从内存中删除并进行流式传输。
  • 禁用垃圾回收确实解决了性能下降的问题。 sync.Pool 会是生产环境的首选解决方案吗?在整个运行过程中,我有很多数据需要保存在内存中。
  • @Socci:我没有足够的信息来告诉您sync.Pool 是否是这里的最佳解决方案。从池中获取某些东西需要运行时类型断言,因此您最终可能会将数据包装成一个类型,并将其断言到一个接口,因此您只需要这样做一次,所以是的,这可能是一个解决方案。我也会尝试调整 GOGC 的值,然后比较
猜你喜欢
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
  • 2017-01-16
  • 1970-01-01
  • 2017-05-20
  • 2018-01-02
相关资源
最近更新 更多