为了账号安全,请及时绑定邮箱和手机立即绑定

用于创建集合的 Python 性能比较 - set() 与 {} 文字

用于创建集合的 Python 性能比较 - set() 与 {} 文字

红糖糍粑 2021-09-28 20:48:32
以下的讨论这个问题,让我知道,所以我决定运行一些测试和比较的创建时间set((x,y,z))与{x,y,z}在Python中创建集(我使用Python 3.7)。我使用time和比较了两种方法timeit。两者都与以下结果一致*:test1 = """my_set1 = set((1, 2, 3))"""print(timeit(test1))结果:0.30240735499999993test2 = """my_set2 = {1,2,3}"""print(timeit(test2))结果:0.10771795900000003所以第二种方法几乎比第一种方法快 3 倍。这对我来说是一个非常令人惊讶的差异。set()以这种方式优化 set 文字在方法上的性能在幕后发生了什么?在哪些情况下建议使用哪种方法?* 注意:我只显示timeit测试结果,因为它们是在许多样本上取平均值的,因此可能更可靠,但测试time结果在两种情况下显示出相似的差异。编辑:我知道这个类似的问题,虽然它回答了我原来问题的某些方面,但并没有涵盖所有方面。问题中没有解决集合,并且由于空集合在 python 中没有文字语法,我很好奇使用文字创建集合与使用该set()方法有何不同(如果有的话)。另外,我想知道的是如何处理的元组参数中set((x,y,z)会在幕后,什么是运行时可能产生的影响。Coldspeed 的出色回答帮助解决了问题。
查看完整描述

1 回答

?
温温酱

TA贡献1752条经验 获得超4个赞

(这是对现在已从最初问题中编辑出来的代码的回应)您忘记在第二种情况下调用函数。进行适当的修改,结果如预期:


test1 = """

def foo1():

     my_set1 = set((1, 2, 3))

foo1()

"""    

timeit(test1)

# 0.48808742000255734

test2 = """

def foo2():

    my_set2 = {1,2,3}

foo2()

"""    

timeit(test2)

# 0.3064506609807722

现在,时间差异的原因是因为set()函数调用需要查找符号表,而{...}集合构造是语法的人工制品,并且速度要快得多。


观察反汇编的字节码时,差异很明显。


import dis


dis.dis("set((1, 2, 3))")

  1           0 LOAD_NAME                0 (set)

              2 LOAD_CONST               3 ((1, 2, 3))

              4 CALL_FUNCTION            1

              6 RETURN_VALUE

dis.dis("{1, 2, 3}")

  1           0 LOAD_CONST               0 (1)

              2 LOAD_CONST               1 (2)

              4 LOAD_CONST               2 (3)

              6 BUILD_SET                3

              8 RETURN_VALUE

在第一种情况下,函数调用是由CALL_FUNCTION元组上的指令进行的(1, 2, 3)(它也有自己的开销,虽然很小——它通过 加载为常量LOAD_CONST),而在第二条指令中只是一个BUILD_SET调用,这更多高效的。


回复:您关于元组构建所需时间的问题,我们认为这实际上可以忽略不计:


timeit("""(1, 2, 3)""")

# 0.01858693000394851


timeit("""{1, 2, 3}""")

# 0.11971827200613916

元组是不可变的,因此编译器通过将其作为常量加载来优化此操作——这称为常量折叠(您可以从LOAD_CONST上面的指令中清楚地看到这一点),因此所花费的时间可以忽略不计。这在集合中看不到,因为它们是可变的(感谢@user2357112 指出这一点)。


对于更大的序列,我们看到了类似的行为。{..}与set()必须从生成器构建集合相比,使用集合推导式构造集合的语法更快。


timeit("""set(i for i in range(10000))""", number=1000)

# 0.9775058150407858


timeit("""{i for i in range(10000)}""", number=1000)

# 0.5508635920123197

作为参考,您还可以在更新的版本上使用可迭代解包:


timeit("""{*range(10000)}""", number=1000)

# 0.7462548640323803

然而,有趣的是,set()直接调用时速度更快range:


timeit("""set(range(10000))""", number=1000)

# 0.3746800610097125

这恰好比集合构造更快。您将看到其他序列(例如lists)的类似行为。


我的建议是{...}在构造集合文字时使用集合理解,并作为将生成器理解传递给的替代方法set();而是用于set()将现有序列/可迭代对象转换为集合。


查看完整回答
反对 回复 2021-09-28
  • 1 回答
  • 0 关注
  • 219 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信