【问题标题】:PyTables dealing with data with size many times larger than size of memoryPyTables 处理大小比内存大很多倍的数据
【发布时间】:2013-02-05 16:47:03
【问题描述】:

我试图了解 PyTables 如何管理大于内存大小的数据。 这是 PyTables (link to GitHub) 代码中的注释:

# Nodes referenced by a variable are kept in `_aliveNodes`.
# When they are no longer referenced, they move themselves
# to `_deadNodes`, where they are kept until they are referenced again
# or they are preempted from it by other unreferenced nodes.

还可以在_getNode 方法中找到有用的 cmets。
似乎 PyTables 具有非常智能的 IO 缓冲系统,据我了解,它将用户引用的数据存储在快速 RAM 中作为“aliveNodes”,在需要时将之前和当前未引用的数据保持为“deadNodes”以快速“恢复”它,并且如果请求的键不存在于死或活类别中,则从磁盘读取数据。

我需要一些专业知识,了解 PyTables 在处理大于可用内存的数据时如何准确处理情况。我的具体问题:

  1. deadNode/aliveNode 系统如何工作(常见图片)?
  2. aliveNodes/deadNodes 之间的主要区别是什么,而它们都表示存储在 RAM 中的数据,如果我是对的?
  3. 可以手动调整用于缓冲的 RAM 限制吗?在注释下方,有一段代码从params['NODE_CACHE_SLOTS'] 读取值。它可以由用户以某种方式指定吗?例如,如果我想为其他也需要内存的应用程序留出一些 RAM?
  4. 在什么情况下 PyTables 在处理大量数据时会崩溃或显着变慢 数据的?在我的情况下可以超过内存 100 倍,在这种情况下常见的陷阱是什么?
  5. PyTables 在大小、数据结构以及对被认为是“正确”的数据进行操作以实现最佳性能方面有何用途?
  6. Docs suggests 在每个基本 .append() 循环之后使用 .flush()。这个周期实际上可以有多长?我正在执行一个小基准测试,比较 SQLite 和 PyTables 如何处理使用大 CSV 文件中的键值对创建一个巨大的表。当我使用.flush() 时,在主循环中使用频率较低,PyTables 获得了巨大的加速。那么——对.append()比较大的数据块,然后使用.flush()是正确的吗?

【问题讨论】:

  • 您无法将内容存储在 100 倍可用 RAM 的内存中。但是,PyTables 可以帮助您以块的形式访问数据或以内存有效的方式(有时)将函数应用于您的数据。你想用你的数据做什么?

标签: python io hdf5 pytables


【解决方案1】:

内存结构

从未使用过pytables,但查看源代码:

class _Deadnodes(lrucacheExtension.NodeCache):
    pass

所以看起来 _deadnodes 是使用 LRU 缓存实现的。 LRU == "最近最少使用" 这意味着它将首先丢弃最少使用的节点。来源是here

class _AliveNodes(dict):
    ...

他们将其用作程序中实际运行和表示的节点的自定义字典。

非常简化的示例(节点是字母,缓存中的数字表示条目的陈旧程度):

memory of 4, takes 1 time step
cache with size 2, takes 5 times steps
disk with much much more, takes 50 time steps

get node A //memory,cache miss load from disk t=50
get node B // "" t=100
get node C // "" t=150
get node D // "" t=200
get node E // "" t=250
get node A //cache hit load from cache t=255
get node F //memory, cache miss load from disk t=305
get node G //memory, cache miss load from disk t=355
get node E // in memory t=356 (everything stays the same)

t=200              t=250              t=255
Memory    CACHE    Memory    CACHE    Memory    CACHE
A                  E         A0       E         B0
B                  B                  A
C                  C                  C
D                  D                  D

t=305              t=355              
Memory    CACHE    Memory    CACHE
E         B1       E         G0
A         C0       A         C1
F                  F
D                  G

正如您在现实生活中所知道的,这些结构非常庞大,访问它们所需的时间是以总线周期为单位的,因此是 1/(您电脑的时钟)。

相比之下,访问元素所需的时间是相同的。它在内存中几乎可以忽略不计,缓存更多,磁盘更多。从磁盘读取是整个过程中最长的部分。磁盘和手臂需要移动等。这是一个物理过程而不是电子过程,因为它不是以光速发生的。

在 pytables 中,他们做了类似的事情。他们在 Cython 中编写了自己的缓存算法,它是活动节点(内存)和完整数据(磁盘)之间的中间人。如果命中率太低,则看起来缓存将被关闭,并且在一定数量的周期后它会再次打开。

parameters.py 中,DISABLE_EVERY_CYCLEENABLE EVERY_CYCLELOWEST_HIT_RATIO 变量用于定义 LOWEST_HIT_RATIO 下要禁用的周期数以及等待重新启用的周期数。不鼓励更改这些值。

您应该从中得到的主要信息是,如果您需要对大型数据集进行处理,请确保它们位于相同的节点上。如果你能摆脱它,读入一个块,在那个卡盘上进行处理,得到你的结果,然后加载另一个块。如果你加载块 A,得到另一个块 B,然后再次加载块 A,这将导致最大的延迟。一次只对一块数据进行操作,并将访问和写入保持在最低限度。一旦值在_alivenodes 中,它可以快速修改它,_deadnodes 会慢一点,也不会慢很多。

NODE_CACHE_SLOTS

params['NODE_CACHE_SLOTS'] 定义死节点集的大小。将其追溯到parameters.py 它默认为 64。它表明您可以尝试不同的值并报告回来。您可以更改文件中的值或执行以下操作:

import parameters
parameters.NODE_CACHE_SLOTS = # something else

这只会限制缓存中保留的节点数量。过去你受python堆大小的限制,设置见this

追加/刷新

对于appendflush 确保将行输出到表中。您移动的数据越多,数据从内部缓冲区移动到数据结构所需的时间就越长。它使用其他处理代码调用 H5TBwrite_records 函数的修改版本。我猜测调用的长度决定了输出周期的长度。

请记住,这一切都来自源代码,而不是考虑他们试图做的任何额外的魔法。我从来没有使用过pytables。从理论上讲,它不应该崩溃,但我们并不生活在一个理论世界中。

编辑:

实际上我自己发现了对 pytables 的需求,我在他们的常见问题解答中遇到了this question,这可能会回答您的一些问题。

感谢您向我公开 pytables,如果我在研究这个问题之前遇到 .h5 文件,我将不知道该怎么做。

【讨论】:

    【解决方案2】:

    我不是 PyTable 方面的专家1,但它很可能像 swap memory 一样工作。

    aliveNodes 存在于 RAM 中,而deadNodes 可能以 hdf5 文件(PyTables 使用的二进制文件格式)存储在磁盘上。 每次您需要访问一条数据时,它都需要在 RAM 中。因此,PyTable 检查它是否已经存在 (aliveNodes),如果存在则将其返回给您。否则,它需要恢复数据所在的deadNode。由于 RAM 有限,它可能会杀死(写入磁盘)未使用的aliveNode 以预先腾出一些空间。

    这个过程的原因当然是 RAM 的大小有限。 结果是每次你需要交换一个节点时性能都会受到影响(kill一个节点,revive另一个)。

    要优化性能,您应该尽量减少交换。例如,如果您的数据可以并行处理,您可能只能加载每个节点一次。其他示例:假设您需要遍历一个巨大矩阵的每个元素,该矩阵被分成一个节点网格。那么你最好避免按行或按列访问它的元素,而是一个节点一个节点。

    当然,PyTable 会在后台处理这个问题,因此您不必控制每个节点中的内容(但我鼓励您深入研究这个 NODE_CACHE_SLOTS 变量,至少要了解它是如何工作的)。但通常访问连续的数据比访问分散在各处的数据更快。与往常一样,如果时间性能对您的应用程序来说是一个重要问题,请分析您的代码。


    1 翻译:我对 PyTables 几乎一无所知

    【讨论】:

      【解决方案3】:

      我也不是 PyTable 方面的专家,Simon 似乎已经很好地涵盖了交换内存的概念,但是如果您想要一个旨在处理太大而无法放入内存的数据的算法的具体示例,我会建议查看外部排序。

      基本思想是这样的:您不能将所有数据都放入内存中,但您需要对其进行排序。但是,您可以将 一些 数据放入内存中,以大小为 k 的块的形式存储。假设有 j 个这样的块。

      • 将数据拆分为大小为 k 的块。
      • 对于每个块,将其放入内存并对其进行排序(例如使用快速排序或其他方式),然后将其排序后的版本写回磁盘。

      现在,我们有 j 块已排序的数据,我们希望将它们合并成一个长的已排序数据。这个问题听起来像归并排序!所以,

      • 将每个 j 排序块中的最小值放入内存
      • 找出这些 j 值中的最小值。那是最小的数据!因此,将其写入磁盘作为我们排序数据集的开始。
      • 将新写入的值替换为从其块到内存中的下一个最小值(这是交换内存的“交换”位)。

      现在,内存中的数据是最小的 j,除了我们已经写入的那个到磁盘上最终排序的数据集中。所以,如果我们重复这个过程,直到所有数据都写入最终集合,它总是会排序。

      所以,这只是使用内存交换来处理太大而无法放入内存的算法的一个示例。 PyTable 的排序方法大概就是这样的。

      奖励:Heresome 链接to 外部排序的更多解释。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-02-14
        • 2011-06-11
        • 1970-01-01
        • 2015-11-14
        相关资源
        最近更新 更多