【问题标题】:maintaining a large list in python在 python 中维护一个大列表
【发布时间】:2010-03-24 17:59:34
【问题描述】:

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

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

该列表可能非常大(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,)

打印结束于:

扩展到 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 秒 被杀(不是我)

【问题讨论】:

  • 400MB 多大?你的电脑有多少内存?
  • 假设它可以达到2GB。我不希望它浪费所有的内存资源。
  • 我第一次尝试将 4,000,000 个 100 字节的对象放入字典中产生了一个消耗 900MB 的 python 进程。耗时几十秒,访问字典的时间基本是瞬间的。
  • 我的程序中需要大约 10 个这样的列表。

标签: python database zodb


【解决方案1】:

使用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'])

【讨论】:

    【解决方案2】:

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

    【讨论】:

    【解决方案3】:

    我认为 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 变量基本上像字典一样工作,可以通过键访问。您还可以使用tree.keys(min, max) 获取范围内的键,如the ZODB BTrees API documentation 所述。

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

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

    tree = getTree()
    
    node1 = tree[1]
    print node1.p1.p1.p1.p1.p1.p1.p1.p1.p1.value
    

    【讨论】:

    • 我承认这是一个非常低级的描述。我会解决这个问题以使其清楚。
    • 我这里没有示例代码。我现在试着写点东西。
    • 这完全不是我的意思。每个节点没有 4 个指针。列表之外有 4 个“访问节点”(总共 4 个,不是每个节点都有 4 个)。我需要的新解释更好。有没有办法跳过“提交”?
    • 你知道 ZODB 树的索引节点(除了树底部的桶之外的任何树节点)是否总是存储在 RAM 中?如果他们这样做,那么我可以使用上面的“deque”技巧来确保快速顺序访问。例如,当我想要节点[314] 时,我可以将节点[315:(315+100)/100*100] 添加到双端队列。那么,对nodes[315]、nodes[316]、nodes[317]...的所有访问都会非常快。
    • 但是当你在 315 和 316 之间插入一个节点时,索引会是什么? 315.5? (好吧,也许你会遇到足够多的项目的 FP 精度限制)。很容易拥有一个节点的双向链接列表,这些节点只是相互引用并遍历和插入/删除 - 但随后一些列表操作代价高昂(如 len() ,除非你保持更新计数属性)......或随机通过索引访问。
    猜你喜欢
    • 1970-01-01
    • 2012-05-22
    • 2011-05-08
    • 2015-09-11
    • 2016-05-24
    • 1970-01-01
    • 2012-10-26
    • 1970-01-01
    • 2013-07-06
    相关资源
    最近更新 更多