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

yuv*_*gin 18 python performance set python-3.x

关于这个问题的讨论让我感到疑惑,所以我决定运行一些测试并比较创建时间set((x,y,z)){x,y,z}在Python中创建集合(我使用的是Python 3.7).

我使用time和比较了这两种方法timeit.两者都是一致*,结果如下:

test1 = """
my_set1 = set((1, 2, 3))
"""
print(timeit(test1))
Run Code Online (Sandbox Code Playgroud)

结果:0.30240735499999993

test2 = """
my_set2 = {1,2,3}
"""
print(timeit(test2))
Run Code Online (Sandbox Code Playgroud)

结果:0.10771795900000003

所以第二种方法几乎比第一种方法快3倍.这对我来说是一个非常惊人的差异.幕后发生了什么,以这种方式优化设置文字的性能set()?哪种情况最合适?

*注意:我只显示timeit测试结果,因为它们在许多样本上取平均值,因此可能更可靠,但测试时的time结果在两种情况下都显示出类似的差异.


编辑:我知道这个类似的问题,虽然它回答了我原来问题的某些方面,但它没有涵盖所有这些问题.在问题中没有解决集合,并且由于空集在python中没有文字语法,我很好奇(如果有的话)使用文字的集合创建与使用该set()方法不同.另外,我想知道的是如何处理的元组参数set((x,y,z)会在幕后,什么是运行时可能产生的影响.coldspeed的最佳答案有助于澄清问题.

cs9*_*s95 31

(这是为了响应现在已经从初始问题中编辑过的代码)您忘记在第二种情况下调用函数.进行适当的修改,结果如预期:

test1 = """
def foo1():
     my_set1 = set((1, 2, 3))
foo1()
"""    
timeit(test1)
# 0.48808742000255734
Run Code Online (Sandbox Code Playgroud)

test2 = """
def foo2():
    my_set2 = {1,2,3}
foo2()
"""    
timeit(test2)
# 0.3064506609807722
Run Code Online (Sandbox Code Playgroud)

现在,时间差异的原因是因为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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,函数调用由指令作出CALL_FUNCTION的元组(1, 2, 3)(它也带有自己的开销,尽管短轴它加载为常数通过LOAD_CONST),而在所述第二指令仅仅是一个BUILD_SET呼叫,这是更高效.

Re:关于元组构造所花时间的问题,我们看到这实际上可以忽略不计:

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

timeit("""{1, 2, 3}""")
# 0.11971827200613916
Run Code Online (Sandbox Code Playgroud)

元组是不可变的,所以编译器通过加载它作为一个常量,这被称为优化此操作常量合并(可以从清楚地看到这LOAD_CONST上述指令),所以所用的时间是可以忽略不计.设置是不可见的,因为它们是可变的(感谢@ user2357112指出这一点).


对于较大的序列,我们看到类似的行为.{..}使用set comprehension构造集合时,语法更快,而不必set()从生成器构建集合.

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

timeit("""{i for i in range(10000)}""", number=1000)
# 0.5508635920123197
Run Code Online (Sandbox Code Playgroud)

作为参考,您还可以在更新的版本上使用iterable unpacking:

timeit("""{*range(10000)}""", number=1000)
# 0.7462548640323803
Run Code Online (Sandbox Code Playgroud)

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

timeit("""set(range(10000))""", number=1000)
# 0.3746800610097125
Run Code Online (Sandbox Code Playgroud)

这恰好比设定结构快.您将看到其他序列(例如lists)的类似行为.

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

  • @DanielMesejo也许,但我不能确定.在这种情况下,可能不是因为我相信python实习生(缓存)元组,所以在前几个时间之后可能不会导致时间差异太大.但理论上,是的,它会有所贡献. (2认同)
  • Python没有实现`(1,2,3)`元组.你看着不断折叠,而不是实习. (2认同)