【问题标题】:Multiprocessing so slow多处理太慢了
【发布时间】:2019-01-05 08:51:35
【问题描述】:

我有一个执行以下操作的函数:

  • 将文件作为输入并进行基本清理。
  • 从文件中提取所需的项目,然后将它们写入 pandas 数据帧。
  • dataframe最终转换成csv并写入文件夹。

这是示例代码:

def extract_function(filename):  
   with open(filename,'r') as f:  
       input_data=f.readlines()  
   try:
     // some basic searching pattern matching extracting  
     // dataframe creation with 10 columns and then extracted values are filled in
        empty dataframe
     // finally df.to_csv()

if __name__ == '__main__':
   pool_size = multiprocessing.cpu_count()
   filenames=os.listdir("/home/Desktop/input")
   pool=multiprocessing.Pool(pool_size)
   pool.map(extract_function,filenames)
   pool.close()
   pool.join()

input 文件夹中的文件总数为4000。我使用了多处理,因为使用 for 循环 正常运行程序需要一些时间。以下是两种方法的执行时间:

正常 CPU 处理 = 139.22 秒
多处理 = 18.72 秒

我的系统规格是:

英特尔 i5 第 7 代,12gb 内存,1Tb 硬盘,Ubuntu 16.04

在为 4000 个文件运行程序时,所有核心都得到了充分利用(平均每个核心大约 90%)。所以我决定增加文件大小并重复这个过程。这次输入文件号从4000 增加到1,20,000。但是这次在运行代码时,CPU 使用率在启动时不稳定,一段时间后利用率下降(每个核心的平均使用率约为 10%)。 ram 利用率也很低,平均为 4gb 最大值(剩余 8gb 空闲)。使用 4000 个文件作为输入,写入 csv 的文件的速度很快,因为我可以看到一个跳跃或大约 1000 个文件或更多文件。但是以 1,20,000 个文件作为输入,文件写入速度减慢到大约 300 个文件,并且这种减慢呈线性增长,一段时间后文件写入速度瞬间变为 50-70 左右。一直以来,大部分 ram 都是免费的。我重新启动机器并尝试清除任何不需要的僵尸进程,但结果仍然相同。

这是什么原因? 我怎样才能对大文件实现相同的多处理?

注意:
* 每个文件大小平均约为 300kb。
* 每个正在写入的输出文件大约为 200 字节。
* 文件总数为 4080。因此总大小约为 1.2gb。
* 相同的 4080 个文件用于制作副本以获得 1,20,000 个文件。
* 这个程序是一个检查大量文件的多处理的实验。

更新 1

我在功能更强大的机器上尝试了相同的代码。

英特尔 i7 第 8 代 8700、1Tb SSHD 和 60gb 内存。

。文件写入比普通硬盘快得多。该计划采取:

  • 对于 4000 个文件 - 3.7 秒
  • 1,20,000 个文件 - 2 分钟

在实验过程中的某个时间点,我得到了最快的完成时间,即 84 秒。在那个时间点,它给了我一致的结果,同时连续尝试了两次。认为可能是因为我在池大小中正确设置了线程因子的数量,我重新启动并再次尝试。但这一次速度慢了很多。从一个角度来看,在正常运行期间,大约 3000-4000 个文件将在一两秒内写入,但这次它在一秒钟内写入低于 600 个文件。在这种情况下,也根本没有使用 ram。 即使使用了多处理模块,CPU 的平均利用率也只有 3-7% 左右。

【问题讨论】:

  • 我可以推测,但您能否分享extract_function 并提供有关每个文件大小的更多详细信息?当您说input file size was increased from 4000 to 120000 时,您是指文件总数还是单个文件大小?精神力量暗示
  • @selbie 已编辑,请检查
  • @selbie : 并行文件打开/读取是否使其变慢?如果那为什么它适用于 4000 个文件?有没有门槛之类的东西?
  • 显而易见的事情是将pool_size 增加两到三倍。由于大多数情况下,您的代码可能会在 I/O 上被阻塞,因此您可以增加线程数。
  • 相信我,当我这么说的时候,优化代码中任何内容的第一步是衡量它并了解性能瓶颈的实际位置。处理数据的 CPU 时间量很可能与花费在 I/O 上的时间量相比可以忽略不计。如果是这样的话,GPU 就没有多大帮助了。

标签: python pandas parallel-processing multiprocessing python-multiprocessing


【解决方案1】:

与从 RAM 中运行代码和数据相比,从磁盘读取和写入磁盘的速度很慢。与从 CPU 的内部缓存中运行代码和数据相比,它非常慢。

为了加快速度,使用了多个缓存。

  1. 硬盘通常具有内置缓存。 2012 年,我对此进行了一些编写测试。禁用硬盘的写入缓存后,写入速度从 72 MiB/s 下降到 12 MiB/s。
  2. 当今的大多数操作系统都使用其他未被占用的 RAM 作为磁盘缓存。
  3. CPU 也有多个级别的内置缓存。

(通常有一种方法可以禁用缓存 1 和 2。如果您尝试这样做,您会看到读写速度像石头一样下降。)

所以我的猜测是,一旦传递了一定数量的文件,就会耗尽一个或多个缓存,磁盘 I/O 就会成为瓶颈。

要验证,您必须将代码添加到 extract_function 以测量 3 件事:

  • 从磁盘读取数据需要多长时间。
  • 计算需要多长时间。
  • 写入 CSV 需要多长时间。

extract_function 返回这三个数字的元组,然后分析它们。我建议不要使用map,而是使用imap_unordered,这样您就可以在数字可用时立即开始评估。

如果磁盘 I/O 成为问题,请考虑使用 SSD。

【讨论】:

  • 感谢您的洞察力。是否会强制使用垃圾收集清理缓存或在帮助之间使用睡眠间隔分割输入文件大小?
  • @Sooraj 如果遇到 I/O 速度限制,垃圾收集可能无济于事。最坦率地说,在你任何事情之前衡量。如果您不知道问题是什么,就无法解决问题... :-)
  • 我注意到的一件事是,使用 4000 时,核心会跳到 100 并一直保持在那里直到程序完成。但是这个 1,20,000 程序刚开始几秒钟就会出现颠簸,然后全部下降到 15 左右并在整个程序中保持在那里。我会检查您建议的时间并回复您。谢谢!
  • Ubuntu 非常擅长在空闲 RAM 中缓存磁盘块。通过运行free 并检查buff/cache 字段进行检查,iotop 也可以指示磁盘何时被击中。
  • 我认为@RolandSmith 是对的,我想补充一点,如果 I/O 是您的瓶颈,您应该考虑使用feather 格式而不是 csv,因为这样更快读/写
【解决方案2】:

正如@RolandSmith & @selbie 建议的那样,我通过将其替换为数据帧并附加到它来避免 IO 连续写入 CSV 文件。我认为这消除了不一致之处。我按照@CoMartel 的建议检查了 "feather""paraquet" 高性能 IO 模块,但我认为它是用于将大文件压缩成更小的数据帧结构。附加选项不存在。

观察:

  • 程序在第一次运行时运行缓慢连续运行会更快。这种行为是一致的。
  • 我检查了程序完成后运行的一些尾随 python 进程,但找不到。因此,CPU/RAM 中存在某种缓存,使程序在连续运行时执行得更快。

4000 个输入文件的程序首次执行耗时 72 秒,然后平均耗时 14-15 秒 之后的所有连续运行

  • 重新启动系统会清除这些缓存并导致程序在第一次运行时运行速度变慢。

  • 平均新鲜运行时间为 72 秒。但是一旦程序启动就将其杀死,然后运行它在终止后的第一次空运行中花费了 40 秒。所有连续运行后的平均 14 秒。

  • 新运行期间,所有核心利用率将在10-13%左右。但在所有连续运行之后,核心利用率将是100%

检查了 1,20,000 个文件,它遵循相同的模式。所以,现在,矛盾已经解决了。因此,如果需要将此类代码用作服务器,则应先进行空运行以使 CPU/RAM 得到缓存,然后才能开始接受 API 查询以获得更快的结果。

【讨论】:

    猜你喜欢
    • 2016-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多