在Python中最大限度地减少对磁盘的读取和写入,以实现内存密集的操作



背景

我正在为一个计算语言学项目做一个计算密集型的项目,但我遇到的问题很普遍,因此我希望其他人也会感兴趣。

要求

我必须写的这个特定程序的关键方面是它必须:

  1. 阅读一个大型语料库(5G到30G之间,以及可能更大的内容(
  2. 处理每一行的数据
  3. 根据这些处理后的数据,构建大量向量(其中一些向量的维度>4000000(。通常,它正在构建数十万个这样的矢量
  4. 这些矢量必须全部以某种格式或其他格式保存到磁盘

步骤1和2并不难有效地执行:只需使用生成器并具有数据分析管道即可。最大的问题是操作3(以及连接4(

圆括号:技术详细信息

如果构建矢量的实际过程影响解决方案:

对于语料库中的每一行,必须更新一个或多个向量的基权重。

如果你从python列表的角度来考虑它们,那么每一行在处理时,都会通过将一个或多个索引处的这些列表的值增加一个值(根据索引可能不同(来更新一个或更多个列表(如果需要,则创建它们(。

矢量并不相互依赖,语料库行的读取顺序也无关紧要。

尝试的解决方案

当谈到如何做到这一点时,有三个极端:

  1. 我可以在内存中构建所有向量。然后将它们写入磁盘
  2. 我可以直接在磁盘上构建所有向量,使用pickle货架或类似的库
  3. 我可以一次在内存中构建一个向量,并将其写入磁盘,每个向量通过语料库一次

所有这些选择都相当棘手。1只是耗尽了所有的系统内存,它惊慌失措,速度慢到爬行。2太慢了,因为IO操作不快。由于同样的原因,3可能甚至比2慢。

目标

一个好的解决方案包括:

  1. 建造尽可能多的记忆
  2. 内存满后,将所有内容转储到磁盘
  3. 如果再次需要磁盘中的位,请将它们恢复到内存中,以向这些向量添加内容
  4. 返回到1,直到构建完所有向量

问题是我真的不知道该怎么做。担心RAM等系统属性似乎有点不符合逻辑,但如果不考虑这一点,我看不出如何以最佳方式解决这类问题。因此,我真的不知道如何开始做这类事情。

问题

有人知道如何着手解决这类问题吗?我python根本不是这种事情的正确语言?或者,有没有一个简单的解决方案可以最大限度地(在合理的范围内(从内存中完成多少操作,同时最大限度地减少必须从磁盘读取或写入数据的次数?

非常感谢您的关注。我期待着看到斯塔克夫弗洛聪明的头脑能给我带来什么。

其他详细信息

运行此问题的机器通常有20多个内核和大约70G的RAM。这个问题可以并行化(àla MapReduce(,因为可以从语料库的片段中构建一个实体的单独向量,然后添加以获得本应从整个语料库中构建的向量。

问题的一部分涉及在需要进行磁盘写入之前,确定内存中可以构建的数量的限制。python是否提供了任何机制来确定有多少RAM可用?

查看pytables。其中一个优点是,您可以处理存储在磁盘上的大量数据,就好像它在内存中一样。

edit:因为I/O性能将是一个瓶颈(如果不是瓶颈的话(,你会想考虑SSD技术:每秒I/O高,几乎没有寻道时间。您的项目大小非常适合当今价格合理的SSD"驱动器"。

您可能会想到几个库来进行评估:

  • joblib-使并行计算变得简单,并提供透明的输出磁盘缓存和延迟重新评估。

  • mrjob-在AmazonElasticMapReduce或您自己的Hadoop集群上轻松编写Hadoop流作业。

两个想法:

  1. 使用numpy数组表示矢量。它们的内存效率高得多,代价是它们将迫使向量的元素为相同类型(所有int或所有double…(

  2. 执行多个过程,每个过程使用一组不同的矢量。也就是说,选择前1M个向量,只进行涉及它们的计算(你说它们是独立的,所以我认为这是可行的(。然后用第二个1M矢量对所有数据进行另一次遍历。

看来你正处于可以用硬件做什么的边缘。如果您能够描述可用于此任务的硬件(主要是RAM(,这将有所帮助。如果有100k个向量,每个向量都有1M int,这就得到了~370GB。如果多次通过的方法是可行的,并且您有一台16GB RAM的机器,那么它大约是25次通过——如果您有集群,应该很容易并行化。

考虑使用像Redis这样的现有内存数据库解决方案。RAM用完后切换到磁盘的问题以及调整这个过程的技巧应该已经到位了。Python客户端。

此外,这种解决方案可以在不付出太多努力的情况下垂直扩展。

您没有提到这两种方式,但如果没有,您应该为列表使用NumPy数组,而不是本地Python列表,这将有助于加快速度并减少内存使用,同时使您正在进行的任何计算更快更容易。

如果你熟悉C/C++,你也可以看看Cython,它可以让你用C编写部分或全部代码,它比Python快得多,并且与NumPy数组集成得很好。你可能想对你的代码进行评测,找出哪些地方花费的时间最多,并用C.编写这些部分

很难说什么是最好的方法,但当然,你可以在关键部分进行任何加速都会有所帮助。还要记住,一旦RAM耗尽,你的程序将开始在磁盘上的虚拟内存中运行,这可能会导致比程序本身多得多的磁盘I/O活动,所以如果你担心磁盘I/O,你最好的办法可能是确保你在内存中处理的那批数据不会比可用RAM大很多。

使用数据库。这个问题似乎足够大,语言选择(Python、Perl、Java等(不会有什么不同。如果向量的每个维度都是表中的一列,那么添加一些索引可能是个好主意。无论如何,这是大量的数据,处理速度不会非常快。

我建议这样做:

1( 构建您提到的的简单管道

2( 在内存中构建向量,并将它们"刷新"到数据库中。(Redis和MongoDB是不错的候选者(

3( 确定这个过程消耗了多少内存,并相应地进行并行化(或者更好地使用映射/减少方法,或者像芹菜一样的分布式任务队列(

加上前面提到的所有提示(numPy等(

很难说,因为缺少一些细节,例如,这是一个专用的盒子吗?这个过程在几台机器上运行吗?可用内存是否更改?

一般来说,我建议不要重新实现操作系统的作业。

请注意,下一段似乎不适用,因为每次都会读取整个文件:我会测试实现三,给它一个健康的磁盘缓存,看看会发生什么。有了大量的缓存,性能可能不会像您预期的那样糟糕。

您还需要缓存即将需要的昂贵计算。简而言之,当计算出一个可以再次使用的昂贵操作时,您可以将其存储在字典(或者磁盘、memcached等(中,然后在再次计算之前先查看那里。Django文档有一个很好的介绍。

从另一条评论中,我推断你的语料库适合内存,你有一些核心要解决这个问题,所以我会尝试这个:

  • 找到一种方法来记忆你的语料库。这可能是一种带有文件系统的ram磁盘,或者是一个数据库。不知道,哪一个最适合你
  • 有一个小的shell脚本来监控ram的使用情况,并每隔一秒生成以下另一个进程,只要还有x内存(或者,如果你想让事情变得更复杂,y磁盘I/O带宽(:

    • 遍历语料库并构建和编写一些向量
  • 最后,如果需要,你可以收集并组合所有向量(这将是减少部分(

在并行作业之间按大小平均分割语料库(每个核心一个(-并行处理,忽略任何不完整的行(或者,如果无法判断它是否不完整,则忽略每个作业处理的第一行和最后一行(。

这是地图部分。

使用一个作业合并每个早期作业中的20多组向量-这是减少步骤。

您可以从2*N行中释放信息,其中N是并行进程的数量,但您可以通过不添加复杂的逻辑来尝试捕获这些行进行处理来获得好处。

其他人在本页上讨论的许多方法都非常有用,我建议其他需要解决此类问题的人看看它们。

这个问题的一个关键方面是决定何时停止在内存中构建向量(或您正在构建的任何向量(并将内容转储到磁盘。这需要一种(蟒蛇式的(方式来确定一个人还剩多少内存。

事实证明,psutil-python模块就是这么做的。

例如,我想有一个while循环,它将内容添加到队列中,供其他进程处理,直到我的RAM达到80%。下面的伪代码可以完成任务:

while (someCondition):
   if psutil.phymem_usage().percent > 80.0:
      dumpQueue(myQueue,somefile)
   else:
      addSomeStufftoQueue(myQueue,stuff)

通过这种方式,您可以让一个进程跟踪内存使用情况,并决定是时候写入磁盘并释放一些系统内存了(决定缓存哪些向量是一个单独的问题(。

PS。向Sean推荐此模块。

最新更新