【问题标题】:Are locks necessary for writing with fwrite from parallel processes in R?从 R 中的并行进程使用 fwrite 写入是否需要锁?
【发布时间】:2021-08-25 08:47:45
【问题描述】:

我有一个在高性能集群上并行运行的密集模拟任务。

每个线程 (~3000) 都使用 R 脚本编写模拟输出,并使用 data.table 包的 fwrite 函数。

我们的 IT 人员告诉我要使用锁。所以我使用flock包在所有线程都写入文件时锁定文件。

但这创造了一个新的瓶颈。大多数情况下,进程等到可以写入为止。现在我想知道如何评估锁是否真的有必要?对我来说,所有作业超过 90% 的处理时间都花在等待锁上,这似乎很奇怪。

当我只使用fwrite 函数和参数append = T 将结果附加到csv 时,谁能告诉我是否真的需要使用锁?

编辑: 我已经尝试过编写单个文件并在所有工作完成后以各种方式合并它们。但是合并的时间也太长,无法接受。

将所有模拟结果写入一个文件而不加锁似乎仍然是最好的方法。这工作得非常快,并且在没有锁定的情况下进行少量模拟时我没有发现错误。

在没有锁的情况下编写会导致一些在运行数百万次模拟后不会被注意到的问题吗?

【问题讨论】:

  • 这些线程是否写入同一个文件?你不能让每个进程写入不同的文件吗?那么锁应该是不必要的。这些文件可以在后处理步骤中合并。
  • 是的,他们写入一个文件。我已经尝试过您提出的选项。但是读取和合并总计大于 100 mb 的约 100k 文件显然也需要大量时间。所以这个选项在处理时间方面并没有真正的优势。
  • 你是如何阅读和合并它们的?该步骤应该比任何涉及需要锁定的共享文件的方法快得多。当然,最好的方法是使用数据库。
  • 我使用“>>”将文件附加到集群上,我还尝试了一些 R 函数。您对使用数据库有何建议?我考虑了一个数据库,但认为仅将行写入文件可能会再次产生过多的开销。
  • 好吧,我会从每个线程保存到一个单独的文件中,lapply(..., fread) 覆盖文件并使用,例如rbindlist。或者使用其他软件合并文件(sed、awk、...)。

标签: r multithreading parallel-processing data.table hpc


【解决方案1】:

(我开始为此写了几个 cmets,然后决定将它们包装在一个答案中。这不是一个完美的分步解决方案,但您的情况并不是那么简单和快速- 从长远来看,修复可能会产生意想不到的副作用。)

我完全同意依赖文件锁定不是一条好路。即使共享文件系统[1]“完全”支持它们(许多人声称它,但有警告和/或极端情况),它们几乎总是会有某种形式的性能损失。由于您唯一需要所有数据的时间是在数据收集(而不是中间处理)时,所以我认为最简单的方法是写入单个文件。

当整个处理完成后,(a) 将所有文件合并为一个(简单的 bash 脚本)并批量插入到数据库中; (b) 组合成几个小到可以读入 R 的大文件(同样是 bash 脚本);或 (c) 逐个文件插入数据库。

  1. 将所有文件合并为一个大文件。使用bash,这可能很简单

    find mypath -name out.csv -print0 | xargs -0 cat > onebigfile.csv
    

    其中mypath 是包含所有文件的目录,每个进程都在唯一的子目录中创建自己的out.csv 文件。这不是一个完美的假设,但前提是如果每个进程创建一个文件,您应该能够从路径下的所有其他文件/目录中唯一地识别那些输出文件。从那里开始,find ... -print0 | xargs -0 cat > onebigfile.csv 是我相信将它们全部结合起来的最佳方式。

    从这里开始,我认为你有三个选择:

    1. 使用可用于该 DBMS 的最佳批量插入工具插入基于服务器的数据库(postgresql、sql server、mariadb 等)。这是一个全新的讨论(超出本 Q/A 的范围),但可以“正式”(使用工作的公司数据库)或“不太正式”使用基于 docker 的数据库进行项目使用。同样,基于 docker 的数据库可能是一个有趣且冗长的讨论。

    2. 插入基于文件的数据库(sqlite、duckdb)。这两个选项都声称支持的文件大小远远超过您对这些数据所需的大小,并且它们都为您提供了根据需要从 R 查询数据子集的选项。如果您不知道 DBI 包或 DBI 方式做事,我强烈建议从https://dbi.r-dbi.org/https://db.rstudio.com/开始。

    3. 拆分文件,然后分段读取到 R。我不知道您是否可以将整个数据放入 R,但如果可以并且读取它们的行为是障碍,那么

      split --lines=1000000 onebigfile.csv smallerfiles.csv.
      HDR=$(head -n 1 onebigfile.csv
      sed -i -e "1i ${HDR}" smallerfiles.csv.*
      sed -i -e "1d" smallerfiles.csv.aa
      

      其中1000000 是您希望在每个较小文件中的行数。您会找到名为 smallerfiles.csv.aa*.ab*.ac 等的 n 文件(取决于大小,您可能会看到三个或更多字母)。\

      HDR= 和第一个 sed 将标题行添加到所有较小的文件中;因为第一个较小的文件已经有了它,第二个sed 删除了重复的第一行。

  2. 将每个文件单独读入 R 或数据库。要引入 R,这将通过以下方式完成:

    files <- list.files("mypath", pattern = "^out.csv$", recursive = TRUE, full.names = TRUE)
    library(data.table)
    alldata <- rbindlist(lapply(files, fread))
    

    假设 R 可以一次保存所有数据。如果 R 不能(要么这样做,要么只是阅读上面的 onebigfile.csv),那么除了数据库形式[2],你真的别无选择。

    要将它们单独读入 DBMS,您可能会在 bash(嗯,任何 shell,只是不是 R)中执行它,它会比 R 更快。不过,您不妨合并到 @987654341 @ 并执行命令行插入一次。然而,将单个文件插入数据库的一个优点是,给定一个相当简单的 bash 脚本,您可以在其他线程仍在工作时从已完成的线程中读取数据;这提供了中间处理状态提示,如果运行时间很长,可能会让您能够在处理完成之前做一些工作。


注意事项:

  1. “共享文件系统”:我假设这些不在本地文件系统上运行。虽然当然不是不可能,但我处理过的大多数企业高性能系统都是基于某种形式的共享文件系统,无论是 NFS 还是 GPFS 或类似的。

  2. “数据库形式”:从技术上讲,在 R 中存在支持部分读取的磁盘文件格式。虽然据称 vroom:: 可以进行内存映射部分读取,但我怀疑你可能会运行稍后会遇到问题,因为它最终可能会尝试读取超出内存支持的内容。也许disk.frame 可以工作,我不知道。其他格式,例如镶木地板或类似格式,我不完全确定(我也没有经验可以说更多)。

【讨论】:

    猜你喜欢
    • 2013-12-23
    • 1970-01-01
    • 1970-01-01
    • 2011-12-01
    • 2012-02-19
    • 2012-02-01
    • 2019-01-25
    • 2011-02-23
    • 1970-01-01
    相关资源
    最近更新 更多