【问题标题】:Python: How to efficiently calculate sum with conditions?Python:如何有效地计算条件和?
【发布时间】:2013-12-27 04:44:33
【问题描述】:

编辑:我正在处理一个性能敏感案例,它需要使用用户定义的检查点计算数据的总和或最大值。请参考演示代码:

from itertools import izip
timestamp=[1,2,3,4,...]#len(timestamp)=N
checkpoints=[1,3,5,7,..]#user defined
data=([1,1,1,1,...],
      [2,2,2,2,...],
      ...)#len(data)=M,len(data[any])=N
processtype=('sum','max','min','snapshot',...)#len(processtype)=M

def processdata(timestamp, checkpoints, data, processtype):
    checkiter=iter(checkpoints)
    checher=checkiter.next()
    tmp=[0 if t=='sum' else None for t in processtype]
    for x, d in izip(timestamp,izip(*data)):
        tmp =[tmp[i]+d[i] if t=='sum' else
              d[i] if (t=='snapshot'
                   or (tmp[i] is None)
                   or (t=='max' and tmp[i]<d[i])
                   or (t=='min' and tmp[i]>d[i])) else
              tmp[i] for (i,t) in enumerate(processtype)]
        if x>checher:
            yield (checher,tmp)
            checher=checkiter.next()
            tmp=[0 if t=='sum' else None for t in processtype]

基准测试的原始演示:

def speratedsum(iter, condition):
    tmp=0
    for x in iter:
        if condition(x):
            yield tmp
            tmp=0
        else:
            tmp+=x

编辑:感谢@M4rtini 和@Chronial,我在以下测试代码上运行了banchmark:

from timeit import timeit

it=xrange(100001)
condition=lambda x: x % 100 == 0

def speratedsum(it, condition):
    tmp=0
    for x in it:
        if condition(x):
            yield tmp+x
            tmp=0
        else:
            tmp+=x

def test1():
    return list(speratedsum(it,condition))

def red_func2(acc, x):
    if condition(x):
        acc[0].append(acc[1]+x)
        return (acc[0], 0)
    else:
        return (acc[0], acc[1] + x)

def test2():
    return reduce(red_func2, it,([], 0))[0]

def red_func3(l, x):
    if condition(x):
        l[-1] += x
        l.append(0)
    else:
        l[-1] += x
    return l

def test3():
    return reduce(red_func3, it, [0])[:-1]

import itertools
def test4():
    groups = itertools.groupby(it, lambda x: (x-1) / 100)
    return map(lambda g: sum(g[1]), groups)

import numpy as np
import numba
@numba.jit(numba.int_[:](numba.int_[:],numba.int_[:]),
           locals=dict(si=numba.int_,length=numba.int_))
def jitfun(arr,con):    
    length=arr.shape[0]
    out=np.zeros(con.shape[0],int)
    si=0
    for i in range(length):        
        out[si]+=arr[i]
        if(arr[i]>=con[si]):
            si+=1
    return out

conditionlist=[x for x in it if condition(x)]
a=np.array(it, int)
c=np.array(conditionlist,int)
def test5():
    return list(jitfun(a,c))
test5() #warm up for JIT

time1=timeit(test1,number=100)
time2=timeit(test2,number=100)
time3=timeit(test3,number=100)
time4=timeit(test4,number=100)
time5=timeit(test5,number=100)

print "test1:",test1()==test1(),time1/time1
print "test2:",test1()==test2(),time1/time2
print "test3:",test1()==test3(),time1/time3
print "test4:",test1()==test4(),time1/time4
print "test5:",test1()==test5(),time1/time5

输出:

test1: True 1.0
test2: True 0.369117307201
test3: True 0.496470798051
test4: True 0.833137283359
test5: True 34.1052257366

你对我应该去哪里寻找有什么建议吗?谢谢!

编辑:我设法使用带有回调的 numba 解决方案来替换 yield,它是在这里真正有效的最省力的解决方案。所以接受了@M4rtini 的回答。但是要小心 numba 的限制。通过我 2 天的尝试,numba 可以提高 numpy 数组索引迭代性能,但仅此而已。

【问题讨论】:

  • 如果你更看重性能而不是可读性,那么 Python 不是最好的语言选择。
  • 每当 x % 100 == 0 时,您重置 tmp = 0,这是您正在寻找的条件吗?
  • 您需要显示更多代码,这部分不是您的瓶颈所在,如果这实际上需要 0.7 秒才能运行。这段代码对我来说运行时间不到 1 毫秒。
  • 转换为numpy数组,用你的条件做布尔索引,对结果求和。
  • 您正在计时的代码不会简单地提供生成器对象吗?

标签: python performance loops iterator


【解决方案1】:

您似乎很确定这是您的程序的缓慢部分,但标准建议是编写可读性,然后在必要时根据性能需要进行修改 - 在分析之后。

这是我前段时间写的关于使 Python 更快的页面: http://stromberg.dnsalias.org/~dstromberg/speeding-python/

如果您不使用任何第 3 方 C 扩展模块,Pypy 可能是您的绝佳选择。如果您使用的是第 3 方 C 扩展模块,请查看 numba 和/或 Cython。

【讨论】:

    【解决方案2】:
    import numba
    @numba.autojit
    def speratedsum2():
        s = 0
        tmp=0
        for x in xrange(10000):
            if x % 100 == 0:
                s += tmp
                tmp=0
            else:
                tmp+=x
        return s
    
    
    In [140]: %timeit sum([x for x in speratedsum1()])
    1000 loops, best of 3: 625 µs per loop
    
    In [142]: %timeit speratedsum2()
    10000 loops, best of 3: 113 µs per loop
    

    【讨论】:

    • 我试过 numba,它已经显示出显着的改进,应该是研究目的的不错选择。但是考虑到 numba 包的依赖并且目前不支持生成器(yield),对于我目前的情况来说这不是一个好的选择。我对 python 的性能声誉感到困惑,并且怀疑与 C# 相比处理速度较慢是因为我对 python 没有很好的了解。
    • 基于更新的演示案例,numpy 能否提供与 numba 类似的性能?
    【解决方案3】:

    只是为了完成它,这里是一个使用 reduce 的实现(尽管性能应该很糟糕):)

    res = reduce(lambda acc, x:
                (acc[0] + [acc[1]], 0) if condition(x) else
                (acc[0], acc[1] + x),
                iter,
                ([], 0))[0]
    

    这应该快很多,但我不是那么“干净”,因为它会改变累积列表。

    def red_func(l, x):
        if condition(x):
            l.append(0)
        else:
            l[-1] = l[-1] + x
        return l
    res = reduce(red_func, iter, [0])[:-1]
    

    【讨论】:

    • 谢谢!还在消化。
    • 使用 l(lower L) 作为变量是个坏习惯:D
    • 运行时间与公平条件比较:[original: 0.29] [reduce with tuple(list,int): 0.78] [reduce with list: 0.67]
    • 我猜列表操作是python中循环比列表理解、映射和减少慢的原因。它们只是为了避免列表追加、索引。
    • 也就是说python中的循环不慢,但是list操作慢。
    【解决方案4】:

    这是使用itertools.groupbyitertools.imap 的解决方案:

    iter = xrange(0, 10000)
    groups = itertools.groupby(iter, lambda x: x / 100)
    sums = itertools.imap(lambda g: sum(list(g[1])[1:]), groups)
    

    请注意,它产生的结果略有不同;结果列表中不会有前导零,并且会产生一个额外的组,因为您没有产生最后一组。

    【讨论】:

    • 感谢 groupby 方法,并为我的不良条件示例感到抱歉。在我更新的条件示例中,我发现很难实现类似于“lambda x: x / 100”的东西。
    【解决方案5】:

    你原来的版本可以用groupby解决:

    for key, group in itertools.groupby(iter, condition):
        if not key:
            yield sum(group)
    

    这假定条件返回TrueFalse 或其他一些两种可能性。如果它可以返回 0、1、2、3 或类似的值,您需要先将返回值转换为 bool

    for key, group in itertools.groupby(iter, lambda x: bool(condition(x))):
        #...
    

    groupby 会将具有相同键的项目按顺序分组到一个组中。在这里,我们将条件下为False 的连续项组合在一起,然后得出组的总和。

    这确实错过了连续两个项目是 True 在这种情况下您的原始版本产生 0 的情况。

    【讨论】:

    • 好点,但请在更新的问题中检查我的运行时基准。
    猜你喜欢
    • 2019-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-01
    • 2015-10-25
    • 1970-01-01
    • 2016-12-31
    相关资源
    最近更新 更多