从大字典中弹出 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.isliceitertools.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 项的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

在 2.6 上合并 n 字典和添加值的最快方法 [重复]

获取数组第一项的最快方法是啥? [复制]

在 Access DB 中删除重复项的最快方法

从大表的子集中对随机行进行最快查询 - postgresql

判断一个数组是不是至少有一个重复项的最快算法

在 C# 中将字典映射到数组的最快方法