【问题标题】:speed up list iteration bottleneck加快列表迭代瓶颈
【发布时间】:2022-01-23 18:09:57
【问题描述】:

我在一段代码中遇到了一个瓶颈,它破坏了我的代码性能。我重新编写了该部分,但是在计时之后,情况并没有改善。

问题如下。给定一个固定长度的整数列表

data = [[1,2,3], [3,2,1], [8,1,0], [1,3,4]]

我需要将每个子列表的索引附加到单独的列表中,其次数与其在给定列索引处的列表值一样多。数据中的每一列都有一个单独的列表。

例如,对于上述数据,将有三个结果列表,因为子列表具有三列。

有 4 个子列表,因此我们希望数字 0-3 出现在每个最终列表中。

我们期望从以上数据生成以下三个列表

[[0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3],
[0, 0, 1, 1, 2, 3, 3, 3],
[0, 0, 0, 1, 3, 3, 3, 3]]

我有两种方法:

processed_data = list([] for _ in range(len(data[0])))
for n in range(len(data)):
    sub_list = data[n]
    
    for k, proc_list in enumerate(processed_data):
        for _ in range(sub_list[k]):
            proc_list.append(n)


processed_data = []
for i, col in enumerate(zip(*data)):
    processed_data.append([j for j,count in enumerate(col) for _ in range(count)])

data 列表的平均大小约为 100,000。

有什么方法可以加快速度吗?

【问题讨论】:

    标签: python python-3.x list


    【解决方案1】:

    除非您能够调整输出格式(见下文),否则您无法提高算法的计算复杂度。换句话说,您最多只能将速度提高一个适度的百分比(并且该百分比将与输入的大小无关)。

    我没有看到任何明显的实施问题。我的一个想法是通过预分配输出矩阵来消除大量的append() 调用和逐步列表扩展所产生的开销,但@juanpa.arrivillaga 在他们的评论中建议append() 实际上是在 CPython 上非常优化。如果您在另一个解释器上,您可以尝试一下:您知道列c 的输出列表的长度将等于列c 中所有输入数字的总和。因此,您可以使用[0] * sum_of_input_values_at_column_c 预先分配每个输出列表,然后使用proc_list[i] = n 而不是proc_list.append(n)(并手动增加i)。但是,这确实需要对输入进行两次传递,因此它实际上可能不是一个改进 - 您的问题非常占用内存,因为它的核心计算非常简单。

    你不能提高计算复杂度的原因是它已经是最优的:任何算法都需要花费时间来生成它的输出,所以输出的大小是算法可能有多快的下限.在您的情况下,输出的大小等于输入矩阵中值的总和(当您依赖输入值本身而不是输入值的数量时,通常认为它很糟糕)。这就是你的算法所花费的迭代次数,所以它是最优的。但是,如果此函数的输出将驻留在内存中以供另一个函数使用(而不是写入文件),并且您可以对该函数进行一些调整,则可以改为输出生成器矩阵,其中每个生成器都知道它需要生成sub_list[k] 出现的n。然后,您的算法的复杂性与输入矩阵的大小成正比(但消耗输出仍将花费与生成完整输出相同的时间)。

    【讨论】:

    • 优秀的答案,但有一点,通常预调用与.append 并不快.append 已经摊销了 O(1),并且迭代两次的成本(在 python 级别上!)扼杀了任何潜在的收益。至少在 CPython 中,学会爱和信任.append
    • 另外,在 CPython 中,.append 将利用 realloc 使其非常高效...
    • @juanpa.arrivillaga:谢谢——非常有趣!我编辑了我的答案以提及您的观察。
    【解决方案2】:

    也许 itertools 可以通过最大限度地减少循环内的 Python 代码量来加快速度:

    data = [[1,2,3], [3,2,1], [8,1,0], [1,3,4]]
    
    from itertools import chain,repeat,starmap
    result = [ list(chain.from_iterable(starmap(repeat,r))) 
               for r in map(enumerate,zip(*data))  ]
    
    print(result)
               
    [[0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3], 
     [0, 0, 1, 1, 2, 3, 3, 3], 
     [0, 0, 0, 1, 3, 3, 3, 3]]
    

    如果您以与结果行出现的顺序相同的顺序处理输出,则可以将其转换为生成器并直接在主进程中使用:

    iResult = ( chain.from_iterable(starmap(repeat,r)) 
                for r in map(enumerate,zip(*data))  )
                
    for iRow in iResult:           # iRow is also an iterator
        for resultItem in iRow:    
            # Perform your item processing here
            print(resultItem, end=" ")
        print()
    
    0 1 1 1 2 2 2 2 2 2 2 2 3 
    0 0 1 1 2 3 3 3 
    0 0 0 1 3 3 3 3
    

    这将完全避免创建和存储索引列表(即将瓶颈降至零)。 但前提是你按顺序处理结果

    【讨论】:

      猜你喜欢
      • 2021-11-12
      • 2011-01-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-03-06
      • 2019-09-09
      相关资源
      最近更新 更多