【问题标题】:readLines taking up too much storagereadLines 占用太多存储空间
【发布时间】:2021-03-05 11:00:33
【问题描述】:

我正在使用readLines 读取一堆带有 ; 的非常大的 .csv。使用以下代码的分隔符:

read_p <- function(flnm) {
  readLines(flnm)  %>%  str_split(pattern = "; ") 
}

for (i in 1:length(files)){
  dat[[i]] <- read_p(files[[i]])
}

其中files 是文件名向量。代码本身运行得相当快,但它在 R 中占用大约 4GB,而在文件夹中只占用约 500MB - 我在阅读它时是否遗漏了一些东西以避免? 我需要使用 readLines,因为没有标题(所以它不是真正的 csv),并且每行都有不同的长度/列数。

感谢您的帮助!

【问题讨论】:

    标签: r readlines


    【解决方案1】:

    从您之前的(已删除)问题中的test.csv 操作,转换为numeric 后可能会有明显的不同。

    为了记录,文件看起来像

    996; 1160.32; 1774.51; 4321.05; 2530.97; 2817.63; 1796.18; ...
    1008; 1774.51; 1796.18; 1192.42; 1285.69; 1225.96; 2229.92; ...
    1020; 1796.18; 1285.69; 711.67; 1761.44; 1016.74; 1671.90; ...
    1032; 1285.69; 1761.44; 1016.74; 1671.90; 725.51; 2466.49; ...
    1044; 1761.44; 1016.74; 725.51; 2466.49; 661.82; 1378.85; ...
    1056; 1761.44; 1016.74; 2466.49; 661.82; 1378.85; 972.94; ...
    1068; 2466.49; 661.82; 1378.85; 972.94; 2259.46; 3648.49; ...
    1080; 2466.49; 1378.85; 972.94; 2259.46; 1287.72; 1074.63; ...
    

    虽然真正的 test.csv 有 751 行文本,并且每行有 10001-10017 之间的 ; 分隔字段。这个(未删节的)文件不到 64 MiB。

    读入、解析,然后转换为数字对其对象大小有显着影响:

    object.size(aa1 <- readLines("test.csv"))
    # Warning in readLines("test.csv") :
    #   incomplete final line found on 'test.csv'
    # 67063368 bytes
    
    object.size(aa2 <- strsplit(aa1, "[; ]+"))
    # 476021832 bytes
    
    object.size(aa3 <- lapply(aa2, as.numeric))
    # 60171040 bytes
    

    我们最终得到:

    length(aa3)
    # [1] 751
    
    str(aa3[1:4])
    # List of 4
    #  $ : num [1:10006] 996 1160 1775 4321 2531 ...
    #  $ : num [1:10008] 1008 1775 1796 1192 1286 ...
    #  $ : num [1:10009] 1020 1796 1286 712 1761 ...
    #  $ : num [1:10012] 1032 1286 1761 1017 1672 ...
    

    因此,将其读入全长字符串不会导致内存爆炸,而是将其拆分为每行 10000 多个字段。这是因为每个 character 对象的开销更大/em>:

    ### vec-1, nchar-0
    object.size("")
    # 112 bytes
    
    ### vec-5, nchar-0
    object.size(c("","","","",""))
    # 152 bytes
    
    ### vec-5, nchar-10
    object.size(c("aaaaaaaaaa","aaaaaaaaaa","aaaaaaaaaa","aaaaaaaaaa","aaaaaaaaaa"))
    # 160 bytes
    

    如果我们查看原始数据,我们会看到爆炸式增长:

    object.size(aa1[1])   # whole lines at a time, so one line is 10000+ characters
    # 89312 bytes
    object.size(aa2[[1]]) # vector of 10000+ strings, each between 3-8 characters
    # 633160 bytes
    

    但幸运的是,内存中的数字要小得多:

    object.size(1)
    # 56 bytes
    object.size(c(1,2,3,4,5))
    # 96 bytes
    

    而且它的扩展性要好得多。显然,在 R 的存储中将数据从 453MiB(拆分,字符串)减少到 57MiB(拆分,数字)就足够了。


    在读取这些文件时,您仍然会看到 R 的内存使用量激增。您可以尝试通过在strsplit 之后立即转换为数字来减少它;老实说,我不知道 R 的垃圾收集器(高级编程语言的常见事物)会以多快的速度返回内存,我也不确定根据 R 的 "global string pool" 这将如何表现。但是如果你有兴趣,你可以试试这个你的函数的改编。

    func <- function(path) {
      aa1 <- readLines(path)
      aa2 <- lapply(aa1, function(st) as.numeric(strsplit(st, "[; ]+")[[1]]))
      aa2
    }
    

    (我不保证它不会仍然“增加”你的内存使用量,但也许它不会那么糟糕。)

    然后你的 for 循环的规范替换(虽然那个循环很好)是

    dat <- lapply(files, func)
    

    【讨论】:

    • 嗨@r2evans,非常感谢您的广泛回答。你是绝对正确的,真正占用存储空间的是将数字存储为字符而不是实际的 readLines 函数。使用您的代码,lapply(aa1, function(st) as.numeric(strsplit(st, "[; ]+")[[1]])) 明显更快。同样值得注意的是,对于任何好奇的人来说,在这种情况下,for 循环在内存使用方面似乎比 lapply 更稳定,但这可能与从 Dropbox 文件夹中工作有关。
    猜你喜欢
    • 2020-08-13
    • 2012-05-15
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2022-07-13
    • 1970-01-01
    • 2020-05-12
    • 1970-01-01
    相关资源
    最近更新 更多