【问题标题】:Python - Performance when adding many dicts of lists to a persistent master dictPython - 将许多列表字典添加到持久主字典时的性能
【发布时间】:2019-12-30 20:16:24
【问题描述】:

我有一个字典“更新”算法,我怀疑它不是最有效的方法。当我运行我的程序并不断地向我现有的字典中添加一个新字典时,性能会随着时间的推移而显着降低。我想找到一种更高效的方式。

我的字典更新操作

我有一个循环,每次迭代都处理一个文件,并产生一个“字典列表的字典”。每个主dict键都有一个列表值,其中的项目本身就是dict,其中可以有多个。在此示例中,属于 B 的列表中有两个字典。我可能会处理第一个文件并得到这个结果:

{'A': [{'filename': 6311, 'id': 6634, 'num_transactions': 4969, 'total': 7808}], 
 'B': [{'filename': 6311, 'id': 3578, 'type': 8268, 'diameter': 2281, 'width': 4617}, 
       {'filename': 6311, 'id': 2289, 'type': 1553, 'diameter': 4104, 'width': 8725}]}

然后我可能会处理另一个文件并得到这个:

{'C': [{'filename': 7775, 'id': 177, 'count': 6139, 'needed': 7905}], 
 'B': [{'filename': 7775, 'id': 7540, 'type': 9854, 'diameter': 3729, 'width': 9145}, 
       {'filename': 7775, 'id': 27, 'type': 2380, 'diameter': 7209, 'width': 6023}]}

然后我将这些字典组合成一个主字典,在其中我根据它们的键值不断组合列表。上述两个字典的组合将导致(这里的顺序是任意的,但为了可读性而排序):

{'A': [{'filename': 6311, 'id': 6634, 'num_transactions': 4969, 'total': 7808}], 
 'B': [{'filename': 6311, 'id': 3578, 'type': 8268, 'diameter': 2281, 'width': 4617}, 
       {'filename': 6311, 'id': 2289, 'type': 1553, 'diameter': 4104, 'width': 8725}, 
       {'filename': 7775, 'id': 7540, 'type': 9854, 'diameter': 3729, 'width': 9145}, 
       {'filename': 7775, 'id': 27, 'type': 2380, 'diameter': 7209, 'width': 6023}], 
 'C': [{'filename': 7775, 'id': 177, 'count': 6139, 'needed': 7905}]}

请注意,我必须有一个最终的 master_dict,其中包含我所有字典中的组合数据,这是不可协商的。

算法和性能

以下是生成随机cur_dicts 并不断将其结果添加到master_dict 的完整程序。函数add_to_master_dict() 代表我的更新算法。

import random
import timeit
import matplotlib.pyplot as plt
random.seed(0)

a_keys = ['id', 'num_transactions', 'total']
b_keys = ['id', 'type', 'diameter', 'width']
c_keys = ['id', 'count', 'needed']
key_dict = {'A':a_keys, 'B':b_keys, 'C':c_keys}

def generate_cur_dict(key_dict):
    cur_dict = {}
    filename_int = random.randint(0, 10000)

    for main in random.sample(key_dict.keys(), 
                              random.randint(1, len(key_dict.keys()))):
        cur_dict[main] = []

        num_rows = random.choice([1, 1, random.randint(1, 3)])
        for _ in range(num_rows):
            temp_dict = {}
            temp_dict['filename'] = filename_int
            for k in key_dict[main]:
                temp_dict[k] = random.randint(0, 10000)

            cur_dict[main].append(temp_dict)

    return cur_dict

# Hacky use of variable scope by assuming existence of cur/master_dict, 
# but easiest way to pass to timeit
def add_to_master_dict():
    if not master_dict:   # master_dict is empty
        master_dict.update(cur_dict)
    else:
        for k in cur_dict.keys():
            if k in master_dict:
                # In case of None value rather than a list
                if cur_dict[k] is None:
                    continue
                else:
                    # Combine the two lists based on key
                    master_dict[k] = master_dict[k] + cur_dict[k]
            else:
                # If key not in master dict, just add the cur_dict value to the 
                # master_dict
                master_dict[k] = cur_dict[k]

master_dict = {}           
times = []
for i in range(50001):
    cur_dict = generate_cur_dict(key_dict)
    times.append(timeit.timeit(add_to_master_dict, number=1))
    # Easy visual way to see how much it slows down over time
    if i % 1000 == 0:
        print(i)

plt.figure(figsize=(10, 6))
plt.plot(times)

我知道这不是使用 timeit 最优雅的方式 - 我没有取平均执行,所以有很多变化 - 但我只是想演示这个概念。应该清楚的是,如果您对任何大量迭代运行此程序,add_to_master_dict() 会陷入相当多的困境,因此我可能会在这里查看指数增长以进行更新。

关于如何以(希望)实现线性时间的方式执行更新操作有什么建议吗?我已经能够找到在简单情况下表现良好的 dict/list 更新算法,但对于我的 dict of lists 用例却一无所获。

【问题讨论】:

    标签: python list performance dictionary


    【解决方案1】:

    这一行

    master_dict[k] = master_dict[k] + cur_dict[k]
    

    每次执行时创建一个新列表。扩展现有列表

    master_dict[k] += cur_dict[k]
    

    更快。在我的机器上,执行时间从 1 分钟 46.857 秒变为 8.027 秒。

    我不是专家,但我怀疑这两个版本的代码都在大致* 线性时间内运行。但是在原始代码中,每次执行该行都必须构建一个长度为 n + k 的新列表,而在改进版本中,现有列表扩展了 k 个元素,这需要更少的内存分配和对象构造。

    * 扩展列表以 摊销 线性时间运行 - 请参阅 https://wiki.python.org/moin/TimeComplexity

    【讨论】:

    • 啊,我过于复杂了——虽然我正在处理“字典列表的字典”,但它最终仍然只是将一个列表添加到现有列表中,答案很明确。也感谢对​​ big-O 执行的澄清。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-07
    • 1970-01-01
    • 1970-01-01
    • 2018-08-27
    • 2018-07-07
    • 1970-01-01
    • 2013-09-28
    相关资源
    最近更新 更多