我正在尝试处理由以下代码生成的数据:
for Gnodes in G.nodes() # Gnodes iterates over 10000 values
Gvalue = someoperation(Gnodes)
for Hnodes in H.nodes() # Hnodes iterates over 10000 values
Hvalue =someoperation(Hnodes)
score = SomeOperation on (Gvalue,Hvalue)
dic_score.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
由于字典很大(10000 个键 X 10000 个列表,每个列表有 3 个元素),因此很难将其保存在内存中。我一直在寻找一种解决方案,它在生成键:值(以列表的形式)对时立即存储它们。这里有人建议,Writing and reading a dictionary in specific format (Python) , 将 ZODB 与 Btree 结合使用。
如果这太天真,请容忍我,我的问题是,什么时候应该拨打
transaction.commit()
提交数据?如果我在内循环结束时调用它,则生成的文件非常大(不知道为什么)。这是一个片段:storage = FileStorage('Data.fs')
db = DB(store)
connection = db.open()
root = connection.root()
btree_container = IOBTree
root[0] = btree_container
for nodes in G.nodes()
btree_container[nodes] = PersistentList () ## I was loosing data prior to doing this
for Gnodes in G.nodes() # Gnodes iterates over 10000 values
Gvalue = someoperation(Gnodes)
for Hnodes in H.nodes() # Hnodes iterates over 10000 values
Hvalue =someoperation(Hnodes)
score = SomeOperation on (Gvalue,Hvalue)
btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
transaction.commit()
如果我在两个循环之外都调用它会怎样?就像是:
......
......
score = SomeOperation on (Gvalue,Hvalue)
btree_container.setdefault(Gnodes,[]).append([Hnodes, score, -1 ])
transaction.commit()
将所有数据保存在内存中,直到我调用 transaction.commit() ?同样,我不确定为什么,但这会导致磁盘上的文件变小。
我想最小化内存中保存的数据。任何指导将不胜感激 !
最佳答案
您的目标是使您的流程在内存限制内易于管理。为了能够使用 ZODB 作为工具执行此操作,您需要了解 ZODB 事务的工作原理以及如何使用它们。
为什么您的 ZODB 变得如此之大
首先,您需要了解事务提交在这里做了什么,这也解释了为什么您的 Data.fs 变得如此之大。
ZODB 按事务写出数据,其中任何已更改的持久对象都会写入磁盘。这里重要的细节是已更改的持久对象; ZODB 以持久对象为单位工作。
并非每个 python 值都是持久对象。如果我定义一个直接的 python 类,它不会是持久的,也不会是任何内置的 python 类型,例如 int 或 list。另一方面,您定义的任何继承自 persistence.Persistent
的类。是一个持久对象。 BTrees
一组类,以及 PeristentList
您在代码中使用的类确实继承自 Persistent
.
现在,在事务提交时,任何已更改的持久对象都将作为该事务的一部分写入磁盘。所以任何 PersistentList
已附加到的对象将被完整写入磁盘。 BTrees
更有效地处理这个问题;它们存储桶,它们本身是持久的,而桶又保存实际存储的对象。因此,对于您创建的每几个新节点,都会将一个 Bucket 写入事务,而不是整个 BTree 结构。请注意,由于树中保存的项目本身就是持久对象,因此只有对它们的引用存储在 Bucket 记录中。
现在,ZODB 通过将事务数据附加到 Data.fs
来写入事务数据。文件,它不会自动删除旧数据。它可以通过从存储中查找给定对象的最新版本来构建数据库的当前状态。这就是为什么您的 Data.fs
增长如此之快,您正在写出越来越大的新版本PersistentList
提交事务时的实例。
删除旧数据称为 包装 ,类似于 VACUUM
PostgreSQL 和其他关系数据库中的命令。只需拨打 the .pack()
method在 db
变量删除所有旧版本,或使用 t
和 days
该方法的参数设置限制保留多少历史,第一个是time.time()
您可以打包之前的时间戳(自纪元以来的秒数),以及 days
是从当前时间或 t
保留过去的天数如果指定。由于旧事务中的部分列表被删除,打包应该会大大减少您的数据文件。请注意,打包是一项昂贵的操作,因此可能需要一段时间,具体取决于数据集的大小。
使用事务管理内存
您正在尝试构建一个非常大的数据集,通过使用持久性来解决内存限制,并使用事务尝试将内容刷新到磁盘。然而,通常情况下,使用事务提交表示您已经完成了数据集的构建,您可以将其用作一个原子整体。
您在这里需要使用的是一个保存点。保存点本质上是子事务,在整个事务过程中的一个点,您可以在其中请求将数据临时存储在磁盘上。当您提交事务时,它们将成为永久性的。要创建保存点,请调用 .savepoint
method关于交易:
for Gnodes in G.nodes(): # Gnodes iterates over 10000 values
Gvalue = someoperation(Gnodes)
for Hnodes in H.nodes(): # Hnodes iterates over 10000 values
Hvalue =someoperation(Hnodes)
score = SomeOperation on (Gvalue,Hvalue)
btree_container.setdefault(Gnodes, PersistentList()).append(
[Hnodes, score, -1 ])
transaction.savepoint(True)
transaction.commit()
在上面的例子中,我设置了
optimistic
flag 为 True,意思是:我不打算回滚到这个保存点;某些存储不支持回滚,并且发出不需要的信号会使您的代码在这种情况下工作。另请注意
transaction.commit()
当整个数据集都被处理时发生,这是提交应该实现的。保存点所做的一件事是调用 ZODB 缓存的垃圾收集,这意味着从内存中删除当前未使用的任何数据。
请注意那里的“当前未使用”部分;如果您的任何代码保留变量中的大值,则无法从内存中清除数据。据我从您向我们展示的代码中可以确定,这看起来不错。但我不知道你的操作是如何工作的,也不知道你是如何生成节点的;小心避免在迭代器执行时在内存中构建完整列表,或者构建大型字典,例如引用所有列表列表。
您可以对创建保存点的位置进行一些试验;您可以在每次处理一个时创建一个
HNodes
,或仅在使用 GNodes
完成时像我上面所做的那样循环。您正在根据 GNodes
构建一个列表,所以它会在循环所有 H.nodes()
时保存在内存中无论如何,刷新到磁盘可能只有在您完全构建完成后才有意义。但是,如果您发现需要更频繁地清除内存,则应考虑使用
BTrees.OOBTree.TreeSet
类或 BTrees.IOBTree.BTree
类而不是 PersistentList
将您的数据分解为更持久的对象。一个 TreeSet
是有序的但不是(容易)可索引的,而 BTree
可以通过使用简单的递增索引键用作列表:for i, Hnodes in enumerate(H.nodes()):
...
btree_container.setdefault(Gnodes, IOBTree())[i] = [Hnodes, score, -1]
if i % 100 == 0:
transaction.savepoint(True)
上面的代码使用 BTree 而不是 PersistentList 并且每 100
HNodes
创建一个保存点处理。因为 BTree 使用桶,桶本身就是持久对象,所以整个结构可以更容易地刷新到保存点,而不必为所有 H.nodes()
留在内存中。待处理。
关于python - 何时在 ZODB 中提交数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11254384/