performance - 大内存使用会减慢不相关的代码
问题描述
我正在维护一个 Go 项目的代码,该项目读取和写入大量数据,并且已经成功完成了一段时间。最近,我做了一个更改:在程序开始时将一个包含大约 200 万条记录的 CSV 文件加载到一个带有 struct 值的映射中。此映射仅在 B 部分中使用,但首先在 A 部分中执行。第一部分的运行速度明显比以前慢(处理时间增加了四倍)。这很奇怪,因为那部分逻辑没有改变。我花了一个星期试图解释这是怎么发生的。以下是我采取的步骤(当我提到性能时,我总是指 A 部分,其中不包括将数据加载到内存中的时间,实际上与它无关):
- 该程序在 Docker 容器内的服务器上运行。但是我已经能够在没有容器的情况下在我的笔记本电脑上重现它:与我在内存中没有加载文件数据的情况下运行它相比,性能确实有所下降。
- 服务器有大量的 RAM。尽管加载文件时显然会使用更多内存,但没有达到任何限制。我也没有看到内存使用和磁盘 I/O 出现峰值或其他奇怪的模式。对于这些检查,我使用了 pprof、htop 和 iotop。
- 当加载数据但地图设置为零时,性能再次正常。
- 将数据加载到切片而不是映射中可将性能从 x4 降低到 x2(但内存使用量与映射大致相同)。
- 这让我想知道是否可以在 A 部分的某个地方访问地图/切片,即使它不应该。映射存储在结构类型的字段中。我检查了一下,这个结构总是通过指针传递(包括所有 goroutines)。使其成为全局变量而不是指针字段并不能解决问题。
- 在标准库之外有一个依赖项。问题是图书馆引起的吗?它强制进行一些垃圾收集。禁用它并没有什么不同。我发现了另一个不相关的类似库,使用它作为替代可以提高性能,但是加载文件数据时仍然需要更长的时间。
什么可能导致这种影响或我如何找到它?
解决方案
所以如果我做对了,你的流程看起来像这样:
- 将 200 万行从 CSV 读入 map -> struct
- 运行 A 部分(不需要来自 CSV 的数据)
- 使用 CSV 中的数据运行 B 部分
为什么要在需要之前读取数据,这是第一个问题,但这可能不是重点。
实际上,垃圾收集器通常会访问映射中的 200 万个结构。根据所GOGC
具有的值,垃圾收集器的起搏器组件可能会随着分配的内存量的增加而更频繁地启动。因为这个映射是留给以后使用的,所以 GC 没有什么可做的,但无论如何它都会占用检查数据的周期。您可以做很多事情来验证并解释这种行为 - 所有这些事情都应该可以帮助您排除/确认垃圾收集是否会减慢您的速度。
- 分析代码(显然,对诊断很重要)IIRC,CPU 配置文件更容易显示 GC 干预
- 尝试禁用垃圾收集 (
debug.SetGCPercent(-1)
) - 将地图存储在
sync.Pool
. 这是一种为您设计的类型,用于保留您将手动管理的内容,并移出常规 GC 周期。 - 仅在需要时阅读 CSV,不要在“A 部分”之前阅读
- 流式传输文件,而不是在大量地图中读取它。200万行,在内存中读取所有这些而不是逐行读取有什么价值?
推荐阅读
- c# - 计算更新错误运算符中的百分比相乘
- java - 如何使用 Selenium WD + Java + JUnit 通过 js-doc-upload 按钮上传文件?
- python-3.x - 如何将 sympy 符号图与基于数据的散点图/线图结合起来?
- javascript - Ng Recaptcha - 在 Angular NgModule 中为提供者中的 userValue 动态加载值
- xsd - XSD 验证:总位数和小数位数
- php - PHP数组到没有索引的json
- reactjs - 函数中调度参数的意义何在?
- docker - 在 docker 文件中安装 python 包
- typo3 - 错字3 - tx_news 和图像显示
- mailgun - Mailgun webhook 和实时/登台服务器设置