大量内存使用会降低无关代码的速度

时间:2019-05-27 07:34:04

标签: performance go memory

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

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

在这里,我已绘制了内存中是否包含数据的指标:enter image description here

什么可能导致这种影响?如何找到?

1 个答案:

答案 0 :(得分: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,请勿在“ A部分”
  • 之前阅读
  • 流式传输文件,而不是在庞大的地图中读取文件。 200万行,而不是逐行读取内存中的所有内容,这有什么价值?