PyTables处理的数据比内存大很多倍

2024-09-23 06:37:33 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解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方法中找到有用的注释。
PyTables似乎有非常智能的IO缓冲系统,据我所知,它将用户引用的数据存储在快速RAM中作为“aliveNodes”,将之前和当前未引用的数据保存为“deadNodes”,以便在需要时快速“恢复”它,并且如果请求的密钥不存在于dead或alive类别中,则从磁盘读取数据。在

我需要一些专业知识来了解PyTables在处理比可用内存更大的数据时是如何处理这种情况的。我的具体问题:

  1. 死点/验证节点系统如何工作(普通图片)?在
  2. 如果正确的话,aliveNodes/deadNodes都代表存储在RAM中的数据,那么它们之间的关键区别是什么?在
  3. 缓冲RAM的限制可以手动调整吗?在注释下面,是从params['NODE_CACHE_SLOTS']读取值的代码。它能被用户指定吗?例如,如果我想为其他需要内存的应用程序保留一些RAM?在
  4. 在什么情况下,PyTables在处理大量数据时会崩溃或显著减速 数据的数量?在我的情况下,记忆可以超过100倍,在这种情况下,常见的陷阱是什么?在
  5. PyTables在大小、数据结构以及对被认为是“正确”的数据进行操作以获得最佳性能方面的用途是什么?在
  6. append" rel="nofollow">Docs suggests在每个基本.append()循环后使用.flush()。这个周期到底能持续多久?我执行了一个小的基准测试,比较了SQLite和PyTables如何处理从大的CSV文件创建一个大表的键值对。当我在主循环中使用.flush()时,PyTables获得了巨大的加速。那么-正确吗,先.append()相对较大的数据块,然后使用.flush()?在

Tags: to数据代码by情况pytablesareram
3条回答

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

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

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

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

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

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

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

另外:Heresome链接to更多外部排序的解释。在

记忆结构

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

class _Deadnodes(lrucacheExtension.NodeCache):
    pass

因此,看起来死节点是使用LRU缓存实现的。LRU==“最近最少使用”这意味着它将首先丢弃最少使用的节点。源是here。在

^{pr2}$

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

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

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变量用于定义在最低命中率下要禁用的循环数和等待重新启用的循环数。不鼓励更改这些值。在

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

节点缓存插槽

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

import parameters
parameters.NODE_CACHE_SLOTS = # something else

这只会限制缓存中保存的节点数。如果没有,您将受到python堆大小的限制,请设置为this。在

追加/刷新

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

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

编辑:

实际上,我自己也发现了pytables的需求,我在他们的faq中遇到了this question,这可能会回答您的一些顾虑。在

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

我不是PyTable1的专家,但它的工作方式很可能与swap memory类似。在

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

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

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

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


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

相关问题 更多 >