PyTables处理的数据大小是内存大小的许多倍



我试图了解PyTables如何管理大小大于内存大小的数据。以下是PyTables代码中的注释(链接到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方法中也可以找到有用的注释
PyTables似乎有一个非常智能的IO缓冲系统,据我所知,它将用户引用的数据存储在快速RAM中作为"aliveNodes",将以前和现在未引用的数据保持为"deadNodes"以在需要时快速"恢复"它,并在请求的密钥不在dead或alive类别中时从磁盘读取数据。

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

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

内存结构

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

class _Deadnodes(lrucacheExtension.NodeCache):
    pass

因此,_deadnodes似乎是使用LRU缓存实现的。LRU=";最近最少使用";这意味着它将首先丢弃使用最少的节点。消息来源在这里。

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中编写了自己的缓存算法,Cython是活跃节点(内存)和完整数据(磁盘)之间的中间人。如果命中率太低,那么看起来缓存将被关闭,经过一定数量的循环后,它将再次打开。

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

你应该从中得到的主要东西是,如果你需要在一个大数据集上进行处理,请确保它们在相同的节点上。如果你能逃脱惩罚,读入一个块,在那个chuck上进行处理,得到结果,然后加载另一个块。如果你加载区块A,获得另一个区块B,然后再次加载区块A。这将导致最大的延迟。一次只对一块数据进行操作,并将访问和写入保持在最低限度。一旦一个值在_alivenodes中,修改它会很快,_deadnodes会慢一点,两者都不会慢很多。

NODE_CACHE_slot

CCD_ 12定义了死节点集合的大小。追溯到parameters.py,它默认为64。它指出,你可以尝试不同的价值观,并报告回来。您可以更改文件中的值,也可以执行以下操作:

import parameters
parameters.NODE_CACHE_SLOTS = # something else

这只会限制缓存中保留的节点数。过去,您受到python堆大小的限制,要设置它才能看到这一点。

追加/刷新

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

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

编辑:

事实上,我自己也发现需要pytables,我在他们的faq中遇到了这个问题,这可能会回答你的一些担忧。

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

我不是PyTable1的专家,但它很可能像交换内存一样工作。

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

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

为了优化性能,您应该尽量减少交换。例如,如果您的数据可以并行处理,那么您可能只能加载每个节点一次。另一个例子:想象一下,你需要在一个巨大矩阵的每个元素上循环,这个矩阵被分割成一个节点网格。然后,您最好避免按行或按列访问其元素,而是逐节点访问。

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


1翻译:我对PyTables

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

基本思想是:你不能把所有的数据都放在内存中,但你需要对它进行排序。然而,你可以把内存中的一些数据放在k大小的块中。假设有j个这样的块。

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

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

  • 将j个排序块中每个块的最低值放入内存
  • 找出这些j值中最小的一个。这是最小的数据!所以,把它写到磁盘的某个地方,作为我们排序数据集的开始
  • 将新写入的值替换为块中下一个最小的值(这是交换内存的"交换"位)

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

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

额外的好处:这里有一些链接到更多关于外部排序的解释。

最新更新