python - 创建集合的 Python 性能比较 - set() 与 {} 文字
问题描述
这个问题之后的讨论让我感到疑惑,所以我决定运行一些测试并比较在 Python 中创建集合的创建时间set((x,y,z))
与{x,y,z}
创建集合的时间(我使用的是 Python 3.7)。
time
我使用和比较了这两种方法timeit
。两者都与以下结果一致*:
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
在两种情况下测试时的结果显示出相似的差异。
编辑:我知道这个类似的问题,虽然它回答了我最初问题的某些方面,但并没有涵盖所有问题。问题中没有解决集合,并且由于空集合在 python 中没有文字语法,我很好奇使用文字创建集合(如果有的话)与使用该set()
方法有何不同。另外,我想知道元组参数的处理是如何在set((x,y,z)
幕后发生的,以及它对运行时的可能影响是什么。Coldspeed 的出色回答帮助解决了问题。
解决方案
(这是对现在已从初始问题中编辑的代码的响应)您忘记在第二种情况下调用函数。进行适当的修改,结果如预期:
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
这恰好比集合构造更快。您将看到其他序列(例如list
s)的类似行为。
我的建议是{...}
在构造集合文字时使用集合理解,并作为将生成器理解传递给的替代方法set()
;而是用于set()
将现有序列/可迭代转换为集合。
推荐阅读
- rust - 一次一个字符地遍历整个文件
- c# - 遍历特定的 Azure Blob 存储目录并删除所有文件
- c# - 当 UseShellExecute 为真时,C# 以编程方式打开 cmd.exe 并将命令写入控制台
- java - 设置列表视图
- css - css变量calc var min,基于视口宽高的嵌套计算
- java - 用于切换服务器的反应式 Webflux - 是否有益?
- c# - IdentityModel - RequestClientCredentialsTokenAsync
- windows-10 - ROS 与 Windows 10,如何找到正确的命令
- database - 如何 $match 列表中的项目
- javascript - 保持 Azure AD B2C 登录弹出窗口可见