我得到了一些非常令人惊讶的结果,这些结果似乎表明将迭代器包装在列表中更有效,并且与使用lambda行走它相比,获得它的长度。这怎么可能?直觉会建议分配所有这些列表会更慢。
是的 - 我知道你不能总是这样做,因为迭代器可以是无限的。 :)
from itertools import groupby
from timeit import Timer
data = "abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac"
def rle_walk(gen):
ilen = lambda gen : sum(1 for x in gen)
return [(ch, ilen(ich)) for ch,ich in groupby(data)]
def rle_list(data):
return [(k, len(list(g))) for k,g in groupby(data)]
# randomy data
t = Timer('rle_walk("abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac")', "from __main__ import rle_walk; gc.enable()")
print t.timeit(1000)
t = Timer('rle_list("abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac")', "from __main__ import rle_list; gc.enable()")
print t.timeit(1000)
# chunky blocks
t = Timer('rle_walk("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccc")', "from __main__ import rle_walk; gc.enable()")
print t.timeit(1000)
t = Timer('rle_list("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccc")', "from __main__ import rle_list; gc.enable()")
print t.timeit(1000)
1.42423391342
0.145968914032
1.41816806793
0.0165541172028
答案 0 :(得分:6)
不幸的是,您的rle_walk
有错误;它需要参数gen
但是应该使用参数data
,因此它在错误的输入上运行。另外,让rle_walk
使用rle_list
内联工作的lambda是不公平的。像这样重写:
def rle_walk(data):
return [(k, sum(1 for _ in g)) for k, g in groupby(data)]
def rle_list(data):
return [(k, len(list(g))) for k, g in groupby(data)]
并测试:
data_block = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccc"
data_random = "abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac"
print [[Timer('r("{data}")'.format(data=data),
"from __main__ import {r} as r; gc.enable()".format(r=r)).timeit(1000)
for r in ['rle_walk', 'rle_list']]
for data in (data_block, data_random)]
给出
[[0.02709507942199707, 0.022060155868530273],
[0.12022995948791504, 0.16360306739807129]]
所以我们看到walk
在块状数据上略慢于list
,但在随机数据上略快一些。我猜的原因是,与列表构造函数相比,生成器(在Python中)会产生开销;并且30项目列表的内存开销太小而不会施加任何重大惩罚。
Disassembling这些功能提供了一些见解:
>>> dis.dis(lambda g: (1 for _ in g))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x2b9202a6fe40, file "<stdin>", line 1>)
3 MAKE_FUNCTION 0
6 LOAD_FAST 0 (g)
9 GET_ITER
10 CALL_FUNCTION 1
13 RETURN_VALUE
>>> dis.dis((lambda g: (1 for _ in g)).func_code.co_consts[0])
1 0 SETUP_LOOP 18 (to 21)
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 11 (to 20)
9 STORE_FAST 1 (_)
12 LOAD_CONST 0 (1)
15 YIELD_VALUE
16 POP_TOP
17 JUMP_ABSOLUTE 6
>> 20 POP_BLOCK
>> 21 LOAD_CONST 1 (None)
24 RETURN_VALUE
>>> dis.dis(lambda g: len(list(g)))
1 0 LOAD_GLOBAL 0 (len)
3 LOAD_GLOBAL 1 (list)
6 LOAD_FAST 0 (g)
9 CALL_FUNCTION 1
12 CALL_FUNCTION 1
15 RETURN_VALUE
生成器表单的更大代码量将产生一些影响;虽然列表形式具有用于构造一次性列表的O(log n)因子,但是它将在循环各种迭代器时由k * O(n)因子支配。要做的一件事就是内存分配 fast ,至少对于单线程环境中的小(子页面)分配(CPython必须是GIL)。
答案 1 :(得分:2)
当我将rle_walk
重写为
def rle_walk(gen):
return [(ch, sum(1 for _ in ich)) for ch, ich in groupby(gen)]
然后它比基于列表的版本更快。
计时(使用IPython):
>>> def rle_walk(gen):
... ilen = lambda gen : sum(1 for x in gen)
... return [(ch, ilen(ich)) for ch,ich in groupby(gen)]
...
>>> %timeit rle_walk(data)
10000 loops, best of 3: 94.3 us per loop
>>> def ilen(x): return sum(1 for _ in x)
...
>>> def rle_walk(gen):
... return [(ch, ilen(ich)) for ch,ich in groupby(gen)]
...
>>> %timeit rle_walk(data)
10000 loops, best of 3: 93.4 us per loop
>>> def rle_walk(gen):
... return [(ch, sum(1 for _ in ich)) for ch,ich in groupby(gen)]
...
>>> %timeit rle_walk(data)
10000 loops, best of 3: 83.8 us per loop
>>> def rle_list(data):
... return [(k, len(list(g))) for k,g in groupby(data)]
...
>>> %timeit rle_list(data)
10000 loops, best of 3: 123 us per loop
(请注意,您正在data
向gen
提供groupby
而不是rle_walk
。
答案 2 :(得分:2)
Python中的函数调用开销(与大多数动态语言一样)非常高。
Python中的函数调用开销相对较高,尤其如此 与内置函数的执行速度进行比较。这强烈 建议在适当的情况下,函数应处理数据 聚集体。
在迭代器版本中,您有ilen()
的函数调用,然后使用Python迭代来构建1的列表。
在列表版本中,您有两次内置插件调用,list()
和len()
。内置函数作为本机代码执行,从高度优化的C编译。最重要的是使用内置list()
内置函数将迭代器转换为列表的迭代使用此本机代码完成。