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

时间:2011-09-11 21:08:12

标签: python memory io

背景

我正在为一个计算语言学项目开展一个计算密集的项目,但我遇到的问题非常普遍,因此我希望其他人也能对解决方案感兴趣。

要求

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

  1. 通读一个大型语料库(介于5G和30G之间,以及可能更大的内容)
  2. 处理每行的数据。
  3. 根据该处理数据,构建大量载体(这些载体中的一些载体的维数> 4,000,000)。通常情况下,它正在构建数十万个这样的载体。
  4. 这些向量必须以某种格式或其他格式保存到磁盘上。
  5. 步骤1和2并不难有效:只需使用生成器并拥有数据分析管道。最大的问题是操作3(和连接4)

    括号:技术细节

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

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

    如果按照python列表来考虑它们,每一行在处理时,通过将一个或多个索引处的这些列表的值递增一个值来更新一个或多个列表(如果需要,可以创建它们)(可能会有所不同)基于索引)。

    矢量不依赖于彼此,也不重要读取语料库的顺序。

    尝试解决方案

    关于如何做到这一点有三个极端:

    1. 我可以在内存中构建所有向量。然后将它们写入磁盘。
    2. 我可以直接在磁盘上构建所有向量,使用pickle或一些这样的库。
    3. 我可以一次一个地在内存中构建向量并将其写入磁盘,每个向量一次通过语料库。
    4. 所有这些选择都相当棘手。 1只是耗尽了所有的系统内存,它会引起恐慌和减速。因为IO操作不快,所以2太慢了。出于同样的原因,3甚至可能比2慢。

      目标

      一个好的解决方案将涉及:

      1. 尽可能在记忆中建立。
      2. 内存已满后,将所有内容转储到磁盘。
      3. 如果再次从磁盘需要位,则将它们恢复到内存中以向这些向量添加内容。
      4. 返回1,直到构建完所有向量。
      5. 问题在于我不确定如何解决这个问题。担心像RAM这样的系统属性似乎有点单声道,但我没有看到如何在不考虑这一点的情况下最佳地解决这类问题。结果,我真的不知道如何开始这样的事情。

        问题

        有谁知道如何解决这类问题?我的python根本不是这种东西的正确语言?或者是否有一个简单的解决方案可以最大化从内存中完成多少(在合理范围内),同时最小化必须从磁盘读取数据或写入数据的次数?

        非常感谢您的关注。我期待看到stackoverflow的聪明才智能够引起我的注意。

        其他详细信息

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

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

11 个答案:

答案 0 :(得分:5)

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

编辑:因为I / O性能将成为瓶颈(如果不是瓶颈),您将需要考虑SSD技术:每秒高I / O且几乎没有寻找时间。您的项目规模非常适合当今价格合理的SSD'驱动器'。

答案 1 :(得分:3)

您可能想要评估几个图书馆:

  • joblib - 简化并行计算,并提供透明的磁盘缓存输出和懒惰的重新评估。

  • mrjob - 在Amazon Elastic MapReduce或您自己的Hadoop集群上编写Hadoop流媒体作业变得轻松。

答案 2 :(得分:2)

两个想法:

  1. 使用numpy数组表示向量。它们的内存效率更高,代价是它们会强制向量的元素属于同一类型(所有整数或所有双精度......)。

  2. 进行多次传递,每次传递都有一组不同的向量。也就是说,选择第一个1M向量并仅进行涉及它们的计算(你说它们是独立的,所以我认为这是可行的)。然后用另外的1M向量传递所有数据。

  3. 您似乎处于可以使用硬件的边缘。如果您可以描述可用于此任务的硬件(主要是RAM),将会有所帮助。如果有100k向量,每个向量都是1M ints,这给出了~370GB。如果多次传递方法是可行的并且你有一台16GB RAM的机器,那么它大约需要25次传递 - 如果你有一个集群,应该很容易并行化。

答案 3 :(得分:2)

你没有提到任何一种方式,但如果你不这样做,你应该使用NumPy数组作为列表而不是本机Python列表,这应该有助于加快速度并减少内存使用,以及无论做什么数学你做得更快更容易。

如果您完全熟悉C / C ++,那么您也可以查看Cython,它可以让您用C语言编写部分或全部代码,这比Python快得多,并且与NumPy数组。您可能需要profile您的代码以找出哪些位置花费的时间最多,并将这些部分写在C中。

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

答案 4 :(得分:2)

考虑使用像Redis这样的现有内存数据库解决方案。一旦RAM消失,切换到磁盘的问题和调整此过程的技巧应该已经到位。 Python客户端也是如此。

此外,这种解决方案可以不费力地垂直扩展。

答案 5 :(得分:1)

很难确切地说,因为缺少一些细节,例如。这是一个专用的盒子吗?该过程是否在多台机器上运行?有效记忆会改变吗?

一般情况下,我建议不要重新实现操作系统的工作。

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

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

答案 6 :(得分:1)

使用数据库。这个问题似乎足够大,语言选择(Python,Perl,Java等)不会有所作为。如果向量的每个维度都是表中的列,则添加一些索引可能是个好主意。无论如何,这是很多数据,并且不会很快处理。

答案 7 :(得分:1)

我建议这样做:

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

2)在内存中构造向量并将它们“刷新”到数据库中。 (RedisMongoDB是不错的候选人)

3)确定此过程消耗多少内存并相应地并行化(或者更好地使用map / reduce方法,或像celery这样的分布式任务队列)

加上之前提到的所有提示(numPy等......)

答案 8 :(得分:0)

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

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

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

答案 9 :(得分:0)

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

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

事实证明,psutil python模块只是诀窍。

例如,假设我想要一个while循环,它将一些东西添加到Queue以供其他进程处理,直到我的RAM满80%为止。跟随伪代码将起到作用:

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

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

PS。转发到Sean以建议此模块。

答案 10 :(得分:0)

在并行作业(每个核心一个)之间平均分割语料库 - 并行处理,忽略任何不完整的行(或者如果你不能确定它是否不完整,则忽略每个作业处理的第一行和最后一行)。

这是地图部分。

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

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