从多个集合中累积加权计数的最快方法。计数器对象?

时间:2015-12-22 01:29:16

标签: python performance counter weighted cumulative-sum

我想合并多个collections.Counter对象,其中计数在添加之前用浮点因子加权(参见代码)。每个计数器对象有大约5000个键,大多数(但不是全部)键在所有计数器对象之间共享。

我目前的代码非常慢。我通过使用以下函数按顺序合并它们来合并大约4000个计数器对象:

def addCounters(c1, c2, factor = 1.0):
    merged = c1
    for word, count in c2.iteritems():
        if word in merged:
            merged[word] = float(merged[word]) + float(count) * factor
        else:
            merged[word] = float(count) * factor
    return merged

根据我的cProfile测量值,4181个呼叫将以不可接受的缓慢25秒运行。这非常烦人,因为它也冻结了我的GUI。

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
4181    1.056    0.000   25.088    0.006   MyClass.py:30(addCounters)

有没有人知道更快的方法呢?

3 个答案:

答案 0 :(得分:3)

可能有所帮助的一些事情:

  1. 如果您只是强行float int,请不要经常打开float来电;只要您保证factorfloat
  2. ,Python就会为您进行转换。
  3. 如果您的Counter个对象未使用Counter个特定功能,则转换为dictdefaultdict(int)(或defaultdict(float)此功能)可能有所帮助; dictdefaultdict(在CPython引用解释器中)在C中实现,其中Counter在Python中实现,这增加了所有用途的开销。在本地测试中(在Python 3.5上,确切地说,它可能无法精确匹配2.7性能),使用原始代码,除了使用defaultdict(int)输入和输出之外什么都不做,减少了运行时间从~19秒到〜的相似输入集〜 12秒。
  4. 可能没有意义,但使用x[k] += y * z而不是x[k] = x[k] + y * z避免两个单独的显式查找可能(温和地)提高速度(由于+=的实施方式,实际发生了两次查找,但是字节代码稍微有点效率)
  5. 不要检查word中每个merged是否存在,因为Counter通过返回0来处理此问题,无论密钥是否存在(并defaultdict(int) 1}}或defaultdict(float)会为你创建它)
  6. 忽略类型从Counter更改为输入的其他内容,改进后的代码如下所示:

    def addCounters(c1, c2, factor=1.0):
        # Assume inputs are defaultdict(int), not Counter
        merged = c1
        factor = float(factor)  # In case someone passes non-float default
        for word, count in c2.iteritems():
            merged[word] += count * factor
        return merged
    

    在Python 3.5的本地测试中(只有代码差异使用.items()而不是.iteritems()),在使用defaultdict(int)和进行这些代码更改之间,在类似于您的数据集中描述,总时间从~19秒下降到~5.5秒。可能仍然太长而无法在GUI事件循环中被接受,但是在这方面很难改进。

答案 1 :(得分:1)

我在移动设备上,所以我无法进行配置,但下面的实现似乎有比原版更简化的反汇编字节代码。如果一个简单的词典没问题,可能值得一试。

def addCounters(c1, c2, factor=1.0):
    return dict(c1, **{w:(c1[w] + c*factor) for w,c in c2.iteritems()})

如果您还需要支持普通dict参数(而不仅仅是Counter,其隐含行为类似defaultdict,以便c1[w]始终安全),您可以执行c1.get(w, 0)

def addCounters(c1, c2, factor=1.0):
    return dict(c1, **{w:(c1.get(w, 0) + c*factor) for w,c in c2.iteritems()})

答案 2 :(得分:0)

部分灵感来自F先生,我想出了一些东西,在我的本地计算机上运行了4.3秒。如果有人能让它更快,我仍然会很高兴。我的方法适用于#pragma ms_struct resetdict

基本上,我将密钥“导出”成集,然后将它们分成3组:

  1. collections.Counter
  2. 独有的密钥
  3. c1
  4. 独有的密钥
  5. 两个键都出现了。

    • 第1组的计数已经是最终的,因为我们执行c1 + factor * c2并且c2中没有计数
    • 第2组的计数只需要与因子相乘,因为c1中没有计数
    • 现在,第3组中的密钥保证同时存在于c2c1中,可以轻松计算其计数
    • 最后所有内容都合并在一起
  6. def addCounters(c1,c2,factor = 1.0):

    c2

    我认为它运行得更快的主要原因是,当添加多个计数器/ dicts时,将始终将先前的累积量反馈给方法#make sure factor is float factor = float(factor) #get keys as sets c1keys, c2keys = set(c1.keys()), set(c2.keys()) #keys only in c1 c1only = c1keys.difference(c2keys) #keys only in c2 c2only = c1keys.difference(c1keys) #keys guaranteed in both both = c1keys.intersection(c2keys) #generate dicts for keys which are unique to c1 or c2 c1onlydict = dict((w,c1[w]) for w in c1only) c2onlydict = dict((w,c2[w]*factor) for w in c2only) #merge loners = dict(c1onlydict, **c2onlydict) #now create the directory with keys which exist in both bothdict = dict((key, c1[key] + c2[key]*factor) for key in both) #merge everything return dict(loners, **bothdict) 。因此,c1(最容易创建并且可能具有高度优化的字节码)将变得非常大,而c1onlydictc2onlydict相比之下会很小。