【问题标题】:Accumulating collections Counter in Python becomes slower as accumulated counter increases in size随着累积计数器大小的增加,Python中的累积集合计数器变得更慢
【发布时间】:2019-05-24 18:59:15
【问题描述】:

我有一个 collections.Counter() 对象,它不断地在循环中获取添加的 Counter 对象(累积)。随着循环通过并且累积的 Counter 增加(更多条目),累积 (+=) 操作变得更慢。

一种解决方法是批量使用计数器并累积部分计数器以最终将它们全部添加(减少)。但我想知道为什么会这样(也许底层实现使用了 hashmaps 并且桶大小不是动态的,因此冲突越来越频繁地发生?)

cnt = Counter()
for i in range(len(list_files_txt)):
    t0 = time()
    f = list_files_txt[i]
    print('[{}/{}]'.format(i, len(list_files_txt)))
    with open(f, 'r') as txt_f:
        cnt += Counter(txt_f.read().lower().replace('\n', ' ').split(' '))
    d_t = time() - t0
    print('Time: ', d_t)
    with open('times.txt', 'a') as times_f:
        times_f.write(str(d_t)+'\n')

预期结果:打印的时间在整个循环中是恒定的

实际结果:随着循环的进行,打印时间会增加

实际结果(代码执行):

[0/185187]
Time:  0.0009126663208007812
[1/185187]
Time:  0.0011148452758789062
[2/185187]
Time:  0.0006835460662841797
[3/185187]
Time:  0.0009150505065917969
[4/185187]
Time:  0.0005855560302734375

# A few thousand iterations later...

[14268/185187]
Time:  0.1499614715576172
[14269/185187]
Time:  0.14177680015563965
[14270/185187]
Time:  0.1480724811553955
[14271/185187]
Time:  0.14731359481811523
[14272/185187]
Time:  0.15594100952148438

这是一个展示这种趋势的图:

【问题讨论】:

    标签: python performance collections counter


    【解决方案1】:

    Counter.__iadd__ 包括对self Counter 的线性扫描,以删除计数为非正数的项目。来自cpython/blob/master/Lib/collections/__init__.py

    def _keep_positive(self):
        '''Internal method to strip elements with a negative or zero count'''
        nonpositive = [elem for elem, count in self.items() if not count > 0]
        for elem in nonpositive:
            del self[elem]
        return self
    
    def __iadd__(self, other):
        '''Inplace add from another counter, keeping only positive counts.
        >>> c = Counter('abbb')
        >>> c += Counter('bcc')
        >>> c
        Counter({'b': 4, 'c': 2, 'a': 1})
        '''
        for elem, count in other.items():
            self[elem] += count
        return self._keep_positive()
    

    当然,这样做所花费的时间会随着结果Counter 的大小线性增长。如果您想避免这种行为,请使用update 而不是+=。与+= 类似(与dict.update 不同),Counter.update 添加计数而不是替换条目。与 += 不同,它不会删除非正数。

    # Instead of cnt += Counter(...)
    cnt.update(Counter(txt_f.read().lower().replace('\n', ' ').split(' ')))
    

    事实上,您甚至不需要构建第二个计数器来添加。您可以直接将一个可迭代的元素传递给update,它会将元素计数添加到计数器中的现有计数中:

    cnt.update(txt_f.read().lower().replace('\n', ' ').split(' '))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-31
      • 2017-04-17
      • 1970-01-01
      • 2020-10-08
      • 2014-05-05
      • 1970-01-01
      • 1970-01-01
      • 2019-07-03
      相关资源
      最近更新 更多