如何使用Python并行读取/处理大文件

5
我有一个非常大的文件,大小约为20GB,其中包含超过20百万行,每一行都代表一个单独的序列化JSON
像普通的循环那样逐行读取文件,并对每行数据进行操作需要很长时间
是否有任何先进的方法或最佳实践,可以使用较小的块并以并行方式读取大型文件,以使处理更快?
我正在使用Python 3.6.X。

不行。Python 无法进行真正的并行线程处理。请阅读 https://dev59.com/UGMl5IYBdhLWcg3wzpnV。你可以尝试的另一种方法是将文件分成多个部分,并使用 multiprocessing 运行。 - MT-FreeHK
请问您能否提供更多信息? - ak_slick
任务很简单,只需读取文件并将其转换为JSON格式,然后执行有关机器学习的操作。 - Nodirbek Shamsiev
@MT-FreeHK Python可以使用multiprocessing模块进行真正的并行处理。您可以通过启动仅计算进程负载并观察每个核的利用率来证明这一点。然而,在尝试并行读取文件时,这并没有帮助。 - Michael Martinez
3个回答

3
很遗憾,没有。读取文件并对读取的行进行操作(如json解析或计算)是一个CPU密集型操作,因此没有聪明的asyncio策略可以加速它。理论上,可以利用多进程和多个核来并行读取和处理,但是让多个线程读取同一文件肯定会引起严重问题。因为您的文件太大了,将所有内容存储在内存中,然后并行计算也很困难。
您最好的选择是在开始时将数据(如果可能)分成多个文件,这将打开使用多个核进行并行处理的更安全的选项。抱歉,就我所知,没有更好的答案。

问题不在于计算部分,因为它可以并行化。问题在于I/O,因为除了硬件解决方案(RAID)之外,没有办法并行化它。 - Michael Martinez

1
有几种可能性,但首先要在代码中找到瓶颈并进行优化。也许您的处理过程会做一些缓慢的事情,可以加速处理速度,这将是更好的选择,而不是使用多进程。
如果这没有帮助,您可以尝试:
  1. 使用另一种文件格式。从文本中读取序列化的json不是世界上最快的操作。因此,您可以存储数据(例如在hdf5中),这可以加快处理速度。

  2. 实现多个工作进程,它们可以读取文件的部分内容(worker1读取0-100万行,worker2读取100万-200万行等)。您可以使用joblib或celery进行编排,具体取决于您的需求。整合结果是挑战,您必须看看您的需求是什么(map-reduce风格?)。由于Python没有真正的线程,因此在Python中比在其他语言中更难,因此您可以为此切换语言。


0

在这里,你需要注意的主要瓶颈不是磁盘也不是CPU,而是内存。不是你拥有多少内存,而是硬件和操作系统如何协同工作,将页面从RAM预取到L{1,2,3}缓存中。

如果你使用readline()逐行加载数据,则并行方法的性能会比串行方法更差。原因与硬件+操作系统有关,而不是软件。当CPU需要读取内存时,一定数量的额外数据会被提前获取到CPU的Lx缓存中,以防这些数据稍后可能会被使用。当你采用串行方法时,这些额外数据实际上在缓存中使用。但是当并行readlines()发生时,额外数据在使用之前就被抢占了。因此,它必须稍后再次获取。这对性能有巨大影响。

使并行方法超越串行readline()方法的性能的方法是让并行进程一次读取多行数据到内存中。使用read()而不是readline()。你应该读取多少字节呢?大约是你的L1缓存大小,大约为64K。这样,几页连续的内存可以加载到该缓存中。

然而,如果你用串行的readline()替换成串行的read(),这将优于并行读取。为什么?因为虽然每个核心都有自己的L1缓存,但是核心共享其他L缓存,因此你会遇到同样的问题。进程1将预取以填充缓存,但在它处理完所有数据之前,进程2接管并替换了缓存内容。因此,进程1稍后必须再次获取相同的数据。

串行和并行之间的性能差异可以很容易地看出来: 首先确保整个文件在页面缓存中(free -m:buff/cache)。通过这样做,您完全从方程式中删除了磁盘/块设备层。整个文件都在RAM中。 然后运行一个简单的串行代码并计时。 然后运行一个简单的使用Process()的并行代码并计时。在商品系统上,串行读取将优于并行读取。

这与您的预期相反,对吧?但您必须考虑您所做的假设。您的假设并没有包括有多个核心之间共享的高速缓存和内存总线。这正是瓶颈所在的地方。我们知道磁盘不是瓶颈,因为我们已经将整个文件加载到了RAM的页面缓存中。我们知道CPU不是瓶颈,因为我们正在使用multiprocessing.Process()确保同时执行,每个核心一个进程(vmstat 1,top -d 1%1)。
唯一能从并行方法获得更好性能的方法是确保您正在运行具有单独NuMA节点的硬件,在其中每个核心都有自己的内存总线和高速缓存。
附带说一句,与其他答案声称的相反,Python肯定可以进行真正的计算并行处理,将100%的利用率同时放在每个核心上。这是使用multiprocessing.Process()完成的。 线程池对此无效,因为线程绑定到单个核心并受限于Python的GIL。但multiprocessing.Process()不受GIL限制,并且肯定有效。
另外需要注意的是:将整个输入文件加载到堆中是一种不好的做法。你永远不知道文件是否比 RAM 可以容纳的更大,这会导致内存溢出。因此,不要试图通过这种方式来优化代码的性能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接