【问题标题】:Golang reading csv consuming more than 2x space in memory than on diskGolang 读取 csv 在内存中消耗的空间是磁盘空间的 2 倍以上
【发布时间】:2020-12-17 01:43:41
【问题描述】:

我正在使用 Golang 将大量 CSV 文件加载到结构中。 结构是

type csvData struct {
    Index   []time.Time
    Columns map[string][]float64
}    

我有一个解析器,它使用:

csv.NewReader(file).ReadAll()

然后我遍历行,并将值转换为它们的类型:time.Timefloat64

问题是这些文件在磁盘上占用 5GB 空间。 一旦我将它们加载到内存中,它们就会消耗 12GB!

我使用了ioutil.ReadFile(path),发现这与预期的一样几乎完全是磁盘上的大小。

这是我的解析器的代码,为了便于阅读,省略了错误,如果你能帮助我解决问题:

var inMemoryRepo = make([]csvData, 0)

func LoadCSVIntoMemory(path string) {
    parsedData := csvData{make([]time.Time, 0), make(map[string][]float64)}
    file, _ := os.Open(path)
    reader := csv.NewReader(file)
    columnNames := reader.Read()
    columnData := reader.ReadAll()
    for _, row := range columnData {
        parsedData.Index = append(parsedData.Index, parseTime(row[0])) //parseTime is a simple wrapper for time.Parse
        for i := range row[1:] {                                       //parse non-index numeric columns
            parsedData.Columns[columnNames[i]] = append(parsedData.Columns[columnsNames[i]], parseFloat(columnData[i])) //parseFloat is wrapper for strconv.ParseFloat
        }
    }
    inMemoryRepo = append(inMemoryRepo, parsedData)
}

我尝试通过在函数调用结束时将 columnDatareader 设置为零来进行故障排除,但没有任何变化。

【问题讨论】:

  • 嗯,主要原因是您没有按原样存储数据,而是尝试将其存储为map
  • 你能告诉我更多吗?我是否缺少有关地图如何存储在内存中的重要知识?我会再读一遍,但如果您能给我您的见解,我将不胜感激
  • 我读了一遍,似乎通过调用 runtime.GC() 修复了一些问题。现在它消耗了大约 8GB 的​​内存!谢谢

标签: csv go


【解决方案1】:

很少将整个文件读入内存是个好主意。

如果你的 csv 是 100GiB 怎么办?

如果您的转换不涉及多条记录,也许您可​​以应用以下算法:

open csv_reader (source file)
open csv_writer (destination file)
for row in csv_reader
    transform row
    write row into csv_writer
close csv_reader and csv_write

【讨论】:

    【解决方案2】:

    这并不奇怪。在您的磁盘上只有 CSV 文本的字符(字节)。当您将它们加载到内存中时,您会根据文本创建数据结构。

    例如,float64 值在内存中需要 64 位,即:8 个字节。如果您有输入文本"1",则为 1 个单字节。然而,如果你创建一个等于1float64 值,那仍然会消耗8 个字节。

    此外,strings 存储有一个字符串标头 (reflect.StringHeader),它是 2 个整数值(在 64 位架构上为 16 个字节),并且此标头指向实际的字符串数据。详情请见String memory usage in Golang

    切片也是类似的数据结构:reflect.SliceHeader。标头由 3 个整数值组成,即使切片中没有元素,在 64 位架构上也是 24 字节。

    在此之上的结构可能具有填充(字段必须与某些值对齐),这再次增加了开销。详情请见Spec: Size and alignment guarantees

    Go map 是 hashmaps,同样有相当多的开销,详情见why slice values can sometimes go stale but never map values?,内存使用见How much memory do golang maps reserve?

    【讨论】:

    • 您好,所以我调用了 runtime.GC(),这帮助很大,并将内存使用量从 12GB 减少到 8GB。并且通常计算为 8 * len(column) * 列数仍然大致证实了磁盘大小。只要这是预期的行为,就可以了,我会尝试以其他方式进行优化。感谢您的帮助!
    猜你喜欢
    • 2019-12-01
    • 1970-01-01
    • 2017-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-23
    • 2020-08-28
    • 2016-11-13
    相关资源
    最近更新 更多