3

我需要维护大量 python pickleable 对象。列表太大,无法全部存储在 RAM 中,因此需要一些数据库\分页机制。我需要该机制支持对列表中关闭(附近)区域的快速访问。

该列表应该实现所有 python-list 功能,但大多数时候我将按顺序工作:扫描列表中的某个范围,并在扫描时决定是否要在扫描点插入\弹出一些节点。

该列表可能非常大(2-3 GB),不应一次全部包含在 RAM 中。节点很小(100-200 字节),但可以包含各种类型的数据。

一个很好的解决方案可能是使用 BTree,其中只有最后访问的存储桶被加载到 RAM 中。

使用 SQL 表并不好,因为我需要实现复杂的索引键机制。我的数据不是表格,它是一个简单的 python 列表,具有在特定索引中添加元素以及从特定位置弹出元素的功能。

我尝试了ZODBzc.blist,它们实现了一个基于 BTree 的列表,可以存储在 ZODB 数据库文件中,但我不知道如何配置它,以便上述功能可以在合理的时间内运行。我不需要所有的多线程\事务处理功能。除了我的单线程程序外,没有其他人会接触数据库文件。

谁能解释我如何配置 ZODB\zc.blist 以使上述功能运行得更快,或者向我展示一个不同的大列表实现?

我尝试过的一些快速而肮脏的代码:

import time
import random

NODE_JUMP = 50000
NODE_ACCESS = 10000

print 'STARTING'


random_bytes = open('/dev/urandom', 'rb')

my_list = list()

nodes_no = 0

while True:
    nodes_no += NODE_JUMP
    start = time.time()
    my_list.extend(random_bytes.read(100) for i in xrange(NODE_JUMP))
    print 'extending to %s nodes took %.2f seconds' % (nodes_no, time.time() - start)

    section_start = random.randint(0, nodes_no -NODE_ACCESS -1)
    start = time.time()
    for index in xrange(section_start, section_start + NODE_ACCESS):
        # rotate the string
        my_list[index] = my_list[index][1:] + my_list[index][0]

    print 'access to %s nodes took %.2f seconds' % (NODE_ACCESS, time.time() - start,)

打印结束于:

扩展到 5000000 个节点耗时 3.49 秒
访问 10000 个节点耗时 0.02 秒
扩展到 5050000 个节点耗时 3.98 秒
访问 10000 个节点耗时 0.01 秒
扩展到 5100000 个节点耗时 2.54 秒
访问 10000 个节点耗时 0.01 秒
扩展到 5150000 个节点耗时 2.19 秒
访问 10000 个节点耗时 0.11 秒
扩展到 5200000 个节点耗时 2.49 秒
访问 10000 个节点耗时 0.01 秒
扩展到 5250000 个节点耗时 3.13 秒
访问 10000 个节点耗时 0.05 秒
被杀(不是我)
4

3 回答 3

2

使用 zc.blist 毕竟可以带来不错的效果,并且在创建 DB 时设置“cache_size”选项可以控制将保留在 RAM 中的数据的大小。如果您不经常执行“transaction.commit”,已用 RAM 的大小可能会变大。通过定义一个大的 cache_size 并经常执行 transaction.commit,blist 的最后访问的存储桶将保留在 RAM 中,让您可以快速访问它们,并且使用的 RAM 量不会增长太多。

打包虽然很贵,但是如果你有一个大硬盘,无论如何你不必经常这样做。

这是一些您自己尝试的代码。在后台运行“top”并更改 cache_size 以查看它如何影响已用 RAM 的数量。

import time
import os
import glob
from ZODB import DB
from ZODB.FileStorage import FileStorage
import transaction
from zc.blist import BList

print('STARTING')

random = open('/dev/urandom', 'rb')


def test_list(my_list, loops = 1000, element_size = 100):
    print('testing list')
    start = time.time()
    for loop in xrange(loops):
        my_list.append(random.read(element_size))
    print('appending %s elements took %.4f seconds' % (loops, time.time() - start))

    start = time.time()
    length = len(my_list)
    print('length calculated in %.4f seconds' % (time.time() - start,))

    start = time.time()
    for loop in xrange(loops):
        my_list.insert(length / 2, random.read(element_size))
    print('inserting %s elements took %.4f seconds' % (loops, time.time() - start))

    start = time.time()
    for loop in xrange(loops):
        my_list[loop] = my_list[loop][1:] + my_list[loop][0]
    print('modifying %s elements took %.4f seconds' % (loops, time.time() - start))

    start = time.time()
    for loop in xrange(loops):
        del my_list[0]
    print('removing %s elements took %.4f seconds' % (loops, time.time() - start))

    start = time.time()
    transaction.commit()
    print('committing all above took %.4f seconds' % (time.time() - start,))

    del my_list[:loops]
    transaction.commit()

    start = time.time()
    pack()
    print('packing after removing %s elements took %.4f seconds' % (loops, time.time() - start))

for filename in glob.glob('database.db*'):    
    try:
        os.unlink(filename)
    except OSError:
        pass

db = DB(FileStorage('database.db'),
        cache_size = 2000)

def pack():
    db.pack()

root = db.open().root()

root['my_list'] = BList()

print('inserting initial data to blist')

for loop in xrange(10):
    root['my_list'].extend(random.read(100) for x in xrange(100000))
    transaction.commit()

transaction.commit()

test_list(root['my_list'])
于 2010-03-27T00:21:36.703 回答
0

我认为 ZODB 是使用的工具。它将存储许多任意项目,它处理内存问题。

这是一个工作示例,在这种情况下,我包含了相互引用的对象以及按编号存储在 BTree 中的对象。

import random
from collections import deque

import ZODB
from ZODB.FileStorage import FileStorage
from ZODB.DB import DB
import transaction
import persistent
import BTrees

def random_string(n=100):
    return ''.join([chr(random.randint(0,95)+32) for i in xrange(n)]) 


class Node(persistent.Persistent):
   def __init__(self, value=None):
       if not value:
           self.value =  random_string()

   def setNeighbors(self, refs):
       self.p1 = refs[0]
       self.p2 = refs[1]
       self.p3 = refs[2]
       self.p4 = refs[3]


def getTree():
    storage = FileStorage('c:\\test.zdb')
    db = DB(storage)
    connection = db.open()
    root = connection.root()
    if root.has_key('tree'):
        tree = root['tree']
    else:
        tree = BTrees.OOBTree.OOBTree()
        root['tree'] = tree
        transaction.commit()
    return tree


def fillDB():
    tree = getTree()

    # start with some initial objects.
    nodes = deque([Node(), Node(), Node(), Node()])
    node = Node()

    for n in xrange(20000):
        tree[n] = node           # store the node based on a numeric ID
        node.setNeighbors(nodes) # Make the node refer to more nodes.
        node = nodes.popleft()   # maintain out list of 4 upcoming nodes.
        nodes.append(Node())
        if n % 1000 == 0:
            transaction.commit() # Must commit for data to make it to disk.
            print n
    transaction.commit()
    return tree

此时tree变量基本上像字典一样工作,可以通过键访问。您还可以按照 ZODB BTrees API 文档tree.keys(min, max)中的说明使用某个范围内的键。

您可以通过将每个列表放在rootZODB 返回的对象中的不同键下来存储 10 个列表。该root对象充当 ZODB 对象存储的“网关”。

多亏了 ZODB,您还可以使用对象间引用以及 Btree 索引。例如:

tree = getTree()

node1 = tree[1]
print node1.p1.p1.p1.p1.p1.p1.p1.p1.p1.value
于 2010-03-24T20:07:50.600 回答
0

使用东京内阁怎么样?非常快速和简单,就像列表一样,但专为您想要的而构建。 http://1978th.net/tokyocabinet/

于 2010-03-24T21:54:40.687 回答