【问题标题】:Minimising reading from and writing to disk in Python for a memory-heavy operation最小化在 Python 中对磁盘的读取和写入以进行内存繁重的操作
【发布时间】:2011-09-11 21:08:11
【问题描述】:

背景

我正在为一个计算语言学项目做一个计算量相当大的项目,但我遇到的问题非常普遍,因此我希望其他人也会感兴趣的解决方案。

要求

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

  1. 通读大型语料库(介于 5G 和 30G 之间,可能还有更大的内容)
  2. 处理每一行的数据。
  3. 根据这些处理过的数据,构造大量向量(其中一些向量的维数 > 4,000,000)。通常它会构建数十万个这样的向量。
  4. 这些向量必须全部以某种格式保存到磁盘。

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

括号:技术细节

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

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

如果您从 python 列表的角度来考虑它们,每一行在处理时都会更新一个或多个列表(如果需要,创建它们),方法是将这些列表在一个或多个索引处的值增加一个值(可能不同基于指数)。

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

尝试的解决方案

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

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

所有这些选项都相当棘手。 1 只是用完所有系统内存,它会恐慌并缓慢爬行。 2 太慢了,因为 IO 操作并不快。出于同样的原因,3 可能甚至比 2 还要慢。

目标

一个好的解决方案包括:

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

问题是我不太确定该怎么做。担心诸如 RAM 之类的系统属性似乎有些不合情理,但我看不出如何在不考虑这一点的情况下以最佳方式解决此类问题。结果,我真的不知道如何开始做这种事情。

问题

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

非常感谢您的关注。我期待看到 stackoverflow 的聪明才智可以为我带来什么。

其他详情

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

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

【问题讨论】:

  • 取决于向量的构建方式以及它们是否相互依赖。
  • @knitti 感谢您的提问。我不知道它是否与解决方案有关,但我相信你的直觉。我已将您问题的答案(希望如此!)添加到问题描述中的“技术细节”部分。
  • 您的结果集(向量)需要 TB 区域中的空间(天真估计为 4 000 000 x 100 000 x 1..2 字节),因此通过语料库实际上是较小的问题。你打算怎么写数据?
  • @knitti 向量通常相当稀疏。在实践中,我使用 numpy.sparse.lil_matrix 实例,并将它们编写为 .npy 文件,但我愿意以不同的方式做事。
  • 听起来像是 Hadoop 的一个大的 map/reduce 问题:第一遍,map 从每个输入文件创建一个向量文件,第二遍,reduce 将输出文件合并为一个。

标签: python memory io


【解决方案1】:

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

编辑:因为 I/O 性能将成为瓶颈(如果不是瓶颈),您将需要考虑 SSD 技术:每秒高 I/O 并且几乎没有搜索时间。您的项目大小非常适合当今经济实惠的 SSD“驱动器”。

【讨论】:

  • HDF5 对于此类问题无疑是一种吸引人的格式。我在让 pytables 在我们的实验室计算机上工作时遇到了很多问题,但我正在研究它。谢谢推荐!
  • 在硬件方面,您的项目大小非常适合当今经济实惠的 SSD“驱动器”。因为 I/O 性能将成为瓶颈(如果不是瓶颈),您将需要考虑 SSD 技术:例如this one 每秒高 I/O,几乎没有搜索时间
  • 这个项目很容易生成 gigs 和 gigs 的矢量数据。虽然您是对的,在现阶段对 SSD 技术做这种事情并不是完全疯了,但我们仍然需要购买相当多的 SSD 或相当大的单个 SSD。它们仍然很贵,而且我还看不到自己写了一笔拨款,要求为一个 SSD 提供三个 Grand ;)不过,您是完全正确的。瓶颈是磁盘 IO,随着 SSD 越来越便宜,这种工作会越来越容易做!
  • 另一种减少 IO 瓶颈的方法是序列化读取和写入。我这样做是从“threading.Thread”派生的对象,每个对象负责模拟的一部分,必须先获取“threading.Lock”才能进行任何保存或读取。这样一来,平均寻道时间(寻道次数)就大大减少了,很快“对象”自己就在不再相互阻塞的情况下执行了计算和节省;计算/保存计划的一种简单的自动和自适应优化!
【解决方案2】:

想到几个你可能想要评估的库:

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

  • mrjob - 可以轻松地在 Amazon Elastic MapReduce 或您自己的 Hadoop 集群上编写 Hadoop 流式作业。

【讨论】:

  • 这些听起来很有趣。我一定会看看他们,谢谢!
【解决方案3】:

你没有提到任何一种方式,但如果你没有提到,你应该为你的列表使用NumPy 数组而不是原生 Python 列表,这应该有助于加快速度并减少内存使用,以及制作任何东西数学运算更快更容易。

如果您完全熟悉 C/C++,您还可以查看 Cython,它可以让您用 C 编写部分或全部代码,这比 Python 快得多,并且与 NumPy 数组很好地集成.您可能想profile您的代码以找出哪些地方花费的时间最多,并用 C 语言编写这些部分。

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

【讨论】:

  • 我们为此使用 numpy 稀疏矩阵,因为向量相当稀疏。加速确实很方便,但这里的真正任务似乎是确定(可能在运行时)在需要完成写入之前可以在内存中完成多少任务。
  • 确保我在内存中处理的这批数据不超过可用 RAM 的最佳方法是什么?这真是问题的症结所在……
【解决方案4】:

两个想法:

  1. 使用 numpy 数组来表示向量。它们的内存效率要高得多,但代价是它们会强制向量的元素为同一类型(全部为整数或全部为双精度......)。

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

您似乎已经掌握了使用硬件的能力。如果您能描述您可以使用哪些硬件(主要是 RAM)来完成此任务,将会有所帮助。如果有 100k 个向量,每个向量都有 1M 个整数,这将提供 ~370GB。如果多遍方法是可行的,并且你有一台具有 16GB RAM 的机器,那么大约需要 25 遍——如果你有一个集群,应该很容易并行化。

【讨论】:

  • 我计划运行它的主力机器有 ~70G RAM 和 24 个 Intel Xeon 3.47GHz 内核。
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • 原谅我对数据库的无知,但这种方法不会:a)每次更新向量时都需要磁盘写入,就像解决方案 3 一样? b) 让这些向量的存储变得可怕? (它们中的大多数都相当稀疏,而且数量很多,所以我们希望尽可能减少它们在磁盘上占用的空间)?我对数据库不是很熟悉,所以可能有明显的方法可以解决这些问题......
    • 稀疏度不清楚。数据库是关于磁盘的,但大多数都实现了缓存机制来批量写入磁盘。
    【解决方案7】:

    我建议这样做:

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

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

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

    加上前面提到的所有技巧(numPy 等)

    【讨论】:

      【解决方案8】:

      很难准确地说出,因为缺少一些细节,例如。这是专用盒子吗?该过程是否在多台机器上运行?有效内存有变化吗?

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

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

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

      【讨论】:

      • 选项3的主要问题是它基本上最大化了你需要做的IO操作。每次需要更新向量时,都需要读取向量分量,然后将其写回磁盘(增量后)。对于一些正在构建的实体向量,这可能在语料库中发生很多。在内存中构建一个完整的向量,然后(一次)写入它是最优化的解决方案,但存在一些问题。
      • 1) 盒子不是专用的。它可以作为工作在我研究小组的主力计算机(70G,24 个 CPU)或超级计算机上运行(我想避免这样做)。 2)该过程可能在多台机器上运行,每台机器都为部分语料库(map)构建所有向量,然后最后将它们相加(减少)以获得最终向量。但是,我没有在多台机器上运行进程的经验,所以我不知道如何在其他机器上执行此操作,尽管我确信我可以找到一种特别的方式。我愿意学习是否有规范的方法来做到这一点!
      • 您能否再描述一下您如何“缓存昂贵的计算”?
      • 好吧,我想我还没有完全理解流水线。已更新。
      【解决方案9】:

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

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

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

      【讨论】:

        【解决方案10】:

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

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

        事实证明,psutil python 模块可以解决问题。

        例如,假设我想要一个 while 循环,将内容添加到队列中以供其他进程处理,直到我的 RAM 已满 80%。下面的伪代码可以解决问题:

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

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

        PS。向Sean 推荐此模块。

        【讨论】:

          【解决方案11】:

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

          这是地图部分。

          使用一个作业来合并来自每个先前作业的 20 多组向量 - 这就是归约步骤。

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

          【讨论】:

            猜你喜欢
            • 2023-03-20
            • 2012-02-24
            • 2016-12-09
            • 2017-10-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多