在python中维护一个大的列表

时间:2010-03-24 17:59:35

标签: python database zodb

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

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

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

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

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

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

任何人都可以解释一下如何配置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,)

打印以:

结束
extending to 5000000 nodes took 3.49 seconds
access to 10000 nodes took 0.02 seconds
extending to 5050000 nodes took 3.98 seconds
access to 10000 nodes took 0.01 seconds
extending to 5100000 nodes took 2.54 seconds
access to 10000 nodes took 0.01 seconds
extending to 5150000 nodes took 2.19 seconds
access to 10000 nodes took 0.11 seconds
extending to 5200000 nodes took 2.49 seconds
access to 10000 nodes took 0.01 seconds
extending to 5250000 nodes took 3.13 seconds
access to 10000 nodes took 0.05 seconds
Killed (not by me)

3 个答案:

答案 0 :(得分:2)

毕竟使用zc.blist可以带来好的结果,并且在创建数据库时设置“cache_size”选项控制将保留在RAM中的数据大小。如果你不经常做“transaction.commit”,那么使用的RAM的大小会变得更大。通过定义一个大的cache_size并经常执行transaction.com,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'])

答案 1 :(得分: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变量基本上像字典一样工作,可以通过密钥访问。您还可以使用the ZODB BTrees API documentation所述的tree.keys(min, max)来获取某个范围内的密钥。

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

由于ZODB,您还可以使用对象间引用以及Btree索引。例如:

tree = getTree()

node1 = tree[1]
print node1.p1.p1.p1.p1.p1.p1.p1.p1.p1.value

答案 2 :(得分:0)

使用东京内阁怎么样?非常快速和简单,像列表,但建立在你想要的。 http://1978th.net/tokyocabinet/