从大字典中弹出 N 项的最快方法
Posted
技术标签:
【中文标题】从大字典中弹出 N 项的最快方法【英文标题】:Fastest way to pop N items from a large dict 【发布时间】:2019-08-07 12:27:33 【问题描述】:我有一个大字典 src
(最多 1M 项),我想拿 N 个(典型值为 N=10K-20K)项,将它们存储在一个新字典 dst
中,只留下src
中的剩余项目。拿哪 N 个项目无关紧要。我正在寻找在 Python 3.6 或 3.7 上执行此操作的最快方法。
迄今为止我发现的最快方法:
src = i: i ** 3 for i in range(1000000)
# Taking items 1 by 1 (~0.0059s)
dst =
while len(dst) < 20000:
item = src.popitem()
dst[item[0]] = item[1]
还有什么更好的吗?即使是边际收益也是好的。
【问题讨论】:
为什么这个代码优化问题没有转给codereview.stackexchange.com? (我确实在审计中得到了这个问题,并且显然保留在主 SO 站点)??? @ZF007 优化 -> 优化。除此之外,它在这里是因为有一个明确的问题和一个明确的答案,它更多地是关于语言而不是我的代码。引用一个有很多观点的成员:Stack Overflow 是关于编程的具体问题的地方。如果它是特定的 - 例如,关于特定功能、语言元素、算法等,我的意思不是“关于你的特定程序”,而是关于你程序中的特定元素,它在编程的上下文,而不是程序的上下文 - 那么就可以了。 【参考方案1】:我发现这种方法稍微快一些(-10% 速度),它使用字典理解,它消耗一个循环,使用 range
产生并解包键和值
dst = key:value for key,value in (src.popitem() for _ in range(20000))
在我的机器上:
your code: 0.00899505615234375
my code: 0.007996797561645508
所以大约快 12%,还不错,但不如 Netwave simpler answer 那样不拆包
如果您想在流程中转换键或值,这种方法会很有用。
【讨论】:
【参考方案2】:dict
内部的简单理解就可以了:
dict(src.popitem() for _ in range(20000))
这里有timing tests
setup = """
src = i: i ** 3 for i in range(1000000)
def method_1(d):
dst =
while len(dst) < 20000:
item = d.popitem()
dst[item[0]] = item[1]
return dst
def method_2(d):
return dict(d.popitem() for _ in range(20000))
"""
import timeit
print("Method 1: ", timeit.timeit('method_1(src)', setup=setup, number=1))
print("Method 2: ", timeit.timeit('method_2(src)', setup=setup, number=1))
结果:
Method 1: 0.007701821999944514
Method 2: 0.004668198998842854
【讨论】:
谢谢!希望下一位主持人@Jean-FrançoisFabre ;) 对于我的解决方案,至少我已经提高了 10 倍,而且速度仍然更快。关键是让更短的代码成为可能并依赖原生函数。 @snakecharmerb 根据我的测试,这仍然是弹出 100K 或 500K 项目时最快的解决方案。 您可以通过先保存绑定方法来节省更多时间。f = d.popitem; return dict(f() for _ in range(20000))
.
使用itertools.islice
和itertools.repeat
还要快一点:dict(f() for f in islice(repeat(d.popitem), 20000))
。【参考方案3】:
这还是有点快:
from itertools import islice
def method_4(d):
result = dict(islice(d.items(), 20000))
for k in result: del d[k]
return result
对比其他版本,使用Netwave的测试用例:
Method 1: 0.004459443036466837 # original
Method 2: 0.0034434819826856256 # Netwave
Method 3: 0.002602717955596745 # chepner
Method 4: 0.001974945073015988 # this answer
额外的加速似乎来自避免 C 和 Python 函数之间的转换。通过反汇编我们可以注意到 dict
实例化发生在 C 端,只有 3 个来自 Python 的函数调用。循环使用DELETE_SUBSCR
操作码,而不需要函数调用:
>>> dis.dis(method_4)
2 0 LOAD_GLOBAL 0 (dict)
2 LOAD_GLOBAL 1 (islice)
4 LOAD_FAST 0 (d)
6 LOAD_ATTR 2 (items)
8 CALL_FUNCTION 0
10 LOAD_CONST 1 (20000)
12 CALL_FUNCTION 2
14 CALL_FUNCTION 1
16 STORE_FAST 1 (result)
3 18 SETUP_LOOP 18 (to 38)
20 LOAD_FAST 1 (result)
22 GET_ITER
>> 24 FOR_ITER 10 (to 36)
26 STORE_FAST 2 (k)
28 LOAD_FAST 0 (d)
30 LOAD_FAST 2 (k)
32 DELETE_SUBSCR
34 JUMP_ABSOLUTE 24
>> 36 POP_BLOCK
4 >> 38 LOAD_FAST 1 (result)
40 RETURN_VALUE
与method_2
中的迭代器相比:
>>> dis.dis(d.popitem() for _ in range(20000))
1 0 LOAD_FAST 0 (.0)
>> 2 FOR_ITER 14 (to 18)
4 STORE_FAST 1 (_)
6 LOAD_GLOBAL 0 (d)
8 LOAD_ATTR 1 (popitem)
10 CALL_FUNCTION 0
12 YIELD_VALUE
14 POP_TOP
16 JUMP_ABSOLUTE 2
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE
每个项目都需要一个 Python 到 C 函数调用。
【讨论】:
我正在研究这个!多么同步! @Netwave 你认为这应该是现在接受的答案吗? @IvailoKaramanolev,是的,我们正在寻找最快的,而且确实是这样。以上是关于从大字典中弹出 N 项的最快方法的主要内容,如果未能解决你的问题,请参考以下文章