【问题标题】:Write an efficient python adjacency tree generation script编写一个高效的python邻接树生成脚本
【发布时间】:2013-05-07 10:04:44
【问题描述】:

我正在编写一个脚本,它生成一个包含数百万个项目的列表,然后根据第一个列表生成另一个列表。它非常快地填满内存,脚本无法继续。 我认为将列表直接存储在文件中然后直接在文件行上循环可能是个好主意。 最有效的方法是什么?

编辑:

我正在尝试逐行生成树。 row5_nodes可以得到一百万个item,我不能删除,因为我用它来生成row6_nodes

import random

class Node:
    def __init__(self, id, name, parent=None):
        self.id = id
        self.name = name
        self.parent = parent

def write_roots(root_nodes, roots):
    global index
    index = 0
    for x in xrange(0,roots):
        node = Node(index,"root"+str(x))
        root_nodes.append(node);
        f.write(str(node.id)+","+str(node.name)+","+str(node.parent)+"\n")
        index += 1;
    return

def write_row(parent_nodes, new_nodes, children):
    global index
    for parent_node in parent_nodes:
        for x in xrange(0,children):
            node = Node(index,"cat"+str(parent_node.id)+"-"+str(x), parent_node.id)
            new_nodes.append(node);
            f.write(str(node.id)+","+str(node.name)+","+str(node.parent)+"\n")
            index += 1;
    return

f = open("data.csv", "wb")
roots = 1000
root_nodes =[]
row1_nodes =[]
row2_nodes =[]
row3_nodes =[]
row4_nodes =[]
row5_nodes =[]
row6_nodes =[]
row7_nodes =[]
row8_nodes =[]
row9_nodes =[]

write_roots(root_nodes, roots)
print "1"
write_row(root_nodes, row1_nodes, random.randrange(0,10))
print "2"
write_row(row1_nodes, row2_nodes, random.randrange(0,10))
print "3"
write_row(row2_nodes, row3_nodes, random.randrange(0,10))
print "4"
write_row(row3_nodes, row4_nodes, random.randrange(0,10))
print "5"
write_row(row4_nodes, row5_nodes, random.randrange(0,10))
print "6"
f.close()

【问题讨论】:

  • 第二个进程是否需要随机访问第一个列表,还是可以按顺序处理项目?如果是这样,请使用生成器而不是在内存中实现列表。
  • 最佳解决方案取决于您在构建列表后打算如何处理它们。可能值得详细说明您要实现的目标。
  • 你是什么意思,将列表存储在文件中然后(稍后)再次循环遍历它的最有效方法是什么?我只能想到明显的解决方案来做到这一点。你试过什么?而且我同意,如果您仍然打算处理第一个列表,请直接执行,并且只将最终结果存储在内存之外。
  • 您可以edit您的问题以包含更多信息。不要链接到要点,在您的帖子中包含代码,请参阅How do I format my code blocks? 以获取有关如何格式化您的代码以包含在内的帮助。
  • 您要解决的问题是什么?你是什​​么意思“但是加载文件要快得多”。请描述您正在解决的问题,而不是就您想出的解决方案寻求帮助。

标签: python performance list memory file-io


【解决方案1】:

您的代码正在为节点级别的每一行创建单独的列表,但您永远不需要超过前一行加上您现在生成的内容。

没有必要在内存中保留那么多信息,丢弃不再需要的信息:

import csv
import random

class Node(object):
    _index = 0
    __slots__ = ('id', 'name', 'parent')

    def __init__(self, name, parent=None):
        self.id = Node._index
        Node._index += 1

        self.name = name
        self.parent = parent

def write_roots(roots, writer):
    nodes = []
    for x in xrange(roots):
        node = Node('root{}'.format(x))
        root_nodes.append(node)
        writer.writerow([node.id, node.name, ''])
    return nodes

def write_row(parent_nodes, writer, children):
    nodes = []
    for parent_node in parent_nodes:
        for x in xrange(children):
            node = Node('cat{}-{}'.format(parent_node.id, x), parent_node.id)
            nodes.append(node)
            writer.writerow([node.id, node.name, node.parent])
    return nodes

roots = 1000

with open("data.csv", "wb") as f:
    writer = csv.writer(f)

    nodes = write_roots(roots, writer)

    for i in xrange(9):
        print 'Writing row {}'.format(i + 1)
        nodes = write_row(nodes, writer, random.randrange(1, 11))

这可能仍然不适合内存,因为您正在以指数方式创建项目;您在这里创建多达 1000 * 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 * 10 == 1000^9 == 1 trillion 叶节点!如果您可以在内存中容纳 1.1 万亿个节点,那么上述解决方案应该适合您,但每个节点占用大约 180 字节的内存,加上 1.1 万亿字节用于保存引用的列表索引,占用了 48 TB信息。

在我们解决这个问题之前,我首先想指出我已经改变了一些东西:

  • Node 类现在负责生成新的 id,使用类属性 Node._index 而不是全局属性。
  • 我使用了__slots__ class attribute 来节省内存开销。
  • write_rootswrite_row 函数返回它们生成的新节点集,而不是更改您传入的可变空列表。
  • 使用了csv 模块;您正在编写一个 CSV 文件,使用此模块可以大大简化该任务。
  • csv.writer() 实例作为参数传递给函数,而不是使用文件对象作为全局的函数。
  • 我改用randrange(1, 11) 来避免在一个级别生成0 个子级。如果您想要随机深度,请改为更改外部循环 (xrange(9))。

如果您不关心将节点写入 CSV 文件的顺序,您可以改用生成器。以下版本以深度优先顺序写入节点,而不是第一个版本中的呼吸优先,但使用内存大大减少

import collections

def write_roots(roots, writer):
    for x in xrange(roots):
        node = Node('root{}'.format(x))
        writer.writerow([node.id, node.name, ''])
        yield node

def write_row(parent_nodes, writer, children):
    for parent_node in parent_nodes:
        for x in xrange(children):
            node = Node('cat{}-{}'.format(parent_node.id, x), parent_node.id)
            writer.writerow([node.id, node.name, node.parent])
            yield node

roots = 1000

with open("data.csv", "wb") as f:
    writer = csv.writer(f)

    nodes = write_roots(roots, writer)

    expected_total = leaf_nodes = roots
    for i in xrange(9):
        childcount = random.randrange(1, 11)
        leaf_nodes *= childcount
        expected_total += leaf_nodes
        print 'Generating row {} with {} nodes per parent'.format(i + 1, childcount)
        nodes = write_row(nodes, writer, childcount)

    print 'Writing out {} nodes'.format(expected_total)
    # we need to loop over the last `nodes` generator to have everything written to a file:
    collections.deque(nodes, maxlen=0)  # empty generator without storing anything

此解决方案一次只需要在内存中最多保留 10 个节点,无需更多。

具有较低 randrange() 限制的测试运行在几分之一秒内创建了 50 万个节点。当每个深度选择的随机子节点数接近 10 时,生成器会花费更长的时间,但您可以在一个小时左右的时间内生成一棵完整的树。

您的下一个问题将是磁盘空间问题。例如,一个包含大约 80 亿个节点(平均情况)的 CSV 文件应该只需要 250GB 的存储空间。但是,您可能会生成多达 1.111 万亿个节点,从而生成一个 62TB 的 CSV 文件。

【讨论】:

  • 您的回答并没有解决问题。该列表在最后变得非常大,并吃掉了所有的内存。感谢重构代码:-)
  • @madmed:那是因为你在这里生成了太多的叶子节点。我提出了第二种解决方案,它一次最多只生成 10 个节点。
  • 谢谢,它有效!我之前没有使用集合/双端队列,所以我会尝试更多地了解它。如何跟踪写入操作的进度。
  • 这是一个效率技巧; deque 是在忽略输出的情况下循环生成器的最快方法。你不需要知道双端队列为此做了什么。
【解决方案2】:

另一个深度优先、基于生成器的解决方案...

import random

next_id = 0

def gen(depth, parent_id=None):
    global next_id
    if parent_id is None:
        nodes = 1000
    else:
        nodes = random.randrange(0, 10)
    for i in range(nodes):
        next_id += 1
        if parent_id is None:
            name = 'root%d' % i
            yield '%d, %s, NULL' % (next_id, name)
        else:
            name = 'cat%d-%d' % (parent_id, next_id)
            yield '%d, %s, %s' % (next_id, name, parent_id)
        if depth > 1:
            for s in gen(depth-1, next_id):
                yield s

f = open('data.csv', 'wb')
for l in gen(6):
    f.write('%s\n') % l
f.close()

【讨论】:

    猜你喜欢
    • 2011-01-03
    • 1970-01-01
    • 1970-01-01
    • 2011-04-11
    • 2013-12-29
    • 1970-01-01
    • 2022-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多