10

我试图了解 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”以快速“恢复”它,并且如果请求的键不存在于死或活类别中,则从磁盘读取数据。

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

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

3 回答 3

2

内存结构

从未使用过 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 中编写了自己的缓存算法,它是活动节点(内存)和完整数据(磁盘)之间的中间人。如果命中率太低,那么看起来缓存将被关闭,并且在一定数量的周期后它会再次打开。

parameters.pyDISABLE_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

追加/刷新

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

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

编辑:

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

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

于 2013-02-28T07:49:41.667 回答
1

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

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

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

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

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


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

于 2013-02-23T00:30:41.087 回答
0

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

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

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

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

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

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

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

奖励:这里一些链接外部排序的更多解释。

于 2013-02-26T02:35:51.693 回答