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()将现有序列/可迭代对象转换为集合。
添加回答
举报