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

时间:2018-12-30 13:27:39

标签: python python-3.x performance set

this question之后的讨论让我很纳闷,因此我决定进行一些测试,并比较set((x,y,z)){x,y,z}的创建时间以在Python中创建集合(使用Python 3.7)。

我比较了使用timetimeit的两种方法。 两者均与以下结果一致*:

test1 = """
my_set1 = set((1, 2, 3))
"""
print(timeit(test1))

结果:0.30240735499999993

test2 = """
my_set2 = {1,2,3}
"""
print(timeit(test2))

结果:0.10771795900000003

因此第二种方法比第一种方法快快3倍。 这对我来说是一个惊人的差异。 这样,通过set()方法来优化集合文字的性能到底是怎么回事?在哪种情况下,哪一种是明智的?

*注意: 我只显示timeit测试的结果,因为它们是对许多样本求平均的结果,因此也许更可靠,但是使用time进行测试时的结果在两种情况下都显示出相似的差异。


编辑:我知道this similar question,尽管它回答了我原始问题的某些方面,但并未涵盖所有问题。问题未解决集,并且由于空集在python中没有文字语法,我很好奇(如果有的话)使用文字创建与使用{{1 }} 方法。另外,我想知道set() tuple参数的处理是如何在后台进行的,这可能会对运行时产生什么影响。 Coldspeed给出的好答案有助于解决问题。

1 个答案:

答案 0 :(得分:31)

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

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)进行函数调用(尽管较小,它也有其自身的开销,它通过{{作为常量加载) 1}}),而第二条指令只是一个LOAD_CONST调用,效率更高。

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

BUILD_SET

字符串是不可变的,因此编译器通过将其加载为常量来优化此操作-这称为constant folding(您可以从上面的timeit("""(1, 2, 3)""") # 0.01858693000394851 timeit("""{1, 2, 3}""") # 0.11971827200613916 指令中清楚地看到这一点),因此花费的时间为微不足道。这些集合是可变的,因此看不到它们(感谢@ user2357112指出了这一点)。


对于更大的序列,我们会看到类似的行为。与LOAD_CONST相比,{..}语法在使用集合推导构造集合时要快得多,而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

这恰好比设定的构造要快。对于其他序列(例如list),您将看到类似的行为。

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