为什么 [None] * 10 比 [None for i in range(10)] 快
Why is [None] * 10 faster than [None for i in range(10)]
我想创建一个带有一些初始化值的列表,因为空列表不是 python 中的一个选项。所以我开始考虑哪个会更快:
l = [None for i in range(1000)]
要么
l = [None] * 1000
我尝试使用 timeit
:
对其进行测试
In [56]: timeit.timeit('l = [None] * 1000', number=10000)
Out[56]: 0.04936316597741097
In [58]: timeit.timeit('l = [None for i in range(1000)]', number=10000)
Out[58]: 0.2318978540133685
令我惊讶的是 [None] * 1000
更快。
- 这是为什么(我的性能测试方法是否正确)?
- 有没有更快的方法来初始化 "empty" 列表?
我假设您使用的是 CPython。让我们将生成的 Python 字节码与 dis
module 进行比较。这是第一个版本:
>>> import dis
>>> def f():
... return [None] * 1000
>>> dis.dis(f)
2 0 LOAD_CONST 0 (None)
2 BUILD_LIST 1
4 LOAD_CONST 1 (1000)
6 BINARY_MULTIPLY
8 RETURN_VALUE
这很清楚:构建列表 [None]
(第 0-2 行),并乘以 1000
(第 4-6 行)。
这是第二个版本:
>>> def g():
... return [None for _ in range(1000)]
>>> dis.dis(g)
2 0 LOAD_CONST 1 (<code object <listcomp> at ..., file "<doctest __main__[3]>", line 2>)
2 LOAD_CONST 2 ('g.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (1000)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
这更复杂:创建了一个名为 g.<locals>.<listcomp>
的函数(第 2 行)和我们将在下面看到的代码(第 0 行)(第 4 行)。构建 range(1000)
(第 6-8-10 行)并创建迭代器(第 12 行)。此迭代器被传递给 g.<locals>.<listcomp>
函数(第 14 行)并返回结果(第 16 行)。
让我们看看g.<locals>.<listcomp>
函数:
>>> dis.dis(g.__code__.co_consts[1])
2 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 8 (to 14)
6 STORE_FAST 1 (_)
8 LOAD_CONST 0 (None)
10 LIST_APPEND 2
12 JUMP_ABSOLUTE 4
>> 14 RETURN_VALUE
创建一个空列表(第 0 行),将迭代器参数 (iter(range(1000))
) 压入堆栈(第 2 行),然后开始 for 循环(第 4 行)。循环索引 (_
) 的值存储在本地数组中(第 6 行),None
附加到列表(第 8-10 行),直到循环结束(循环的第 12 行到第 4 行)。
总结一下:
- 第一个版本:乘法;
- 第二个版本:创建一个本地函数,创建一个范围并将迭代器传递给函数;此函数遍历迭代器并逐个附加元素。
第二个版本确实比较慢
注意注意常见的陷阱
>>> A = [[0]] * 3
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0, 1], [0, 1]]
但是:
>>> A = [[0] for _ in range(3)]
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0], [0]]
如果您想知道为什么,请查看上面的字节码。
我想创建一个带有一些初始化值的列表,因为空列表不是 python 中的一个选项。所以我开始考虑哪个会更快:
l = [None for i in range(1000)]
要么
l = [None] * 1000
我尝试使用 timeit
:
In [56]: timeit.timeit('l = [None] * 1000', number=10000)
Out[56]: 0.04936316597741097
In [58]: timeit.timeit('l = [None for i in range(1000)]', number=10000)
Out[58]: 0.2318978540133685
令我惊讶的是 [None] * 1000
更快。
- 这是为什么(我的性能测试方法是否正确)?
- 有没有更快的方法来初始化 "empty" 列表?
我假设您使用的是 CPython。让我们将生成的 Python 字节码与 dis
module 进行比较。这是第一个版本:
>>> import dis
>>> def f():
... return [None] * 1000
>>> dis.dis(f)
2 0 LOAD_CONST 0 (None)
2 BUILD_LIST 1
4 LOAD_CONST 1 (1000)
6 BINARY_MULTIPLY
8 RETURN_VALUE
这很清楚:构建列表 [None]
(第 0-2 行),并乘以 1000
(第 4-6 行)。
这是第二个版本:
>>> def g():
... return [None for _ in range(1000)]
>>> dis.dis(g)
2 0 LOAD_CONST 1 (<code object <listcomp> at ..., file "<doctest __main__[3]>", line 2>)
2 LOAD_CONST 2 ('g.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (1000)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
这更复杂:创建了一个名为 g.<locals>.<listcomp>
的函数(第 2 行)和我们将在下面看到的代码(第 0 行)(第 4 行)。构建 range(1000)
(第 6-8-10 行)并创建迭代器(第 12 行)。此迭代器被传递给 g.<locals>.<listcomp>
函数(第 14 行)并返回结果(第 16 行)。
让我们看看g.<locals>.<listcomp>
函数:
>>> dis.dis(g.__code__.co_consts[1])
2 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 8 (to 14)
6 STORE_FAST 1 (_)
8 LOAD_CONST 0 (None)
10 LIST_APPEND 2
12 JUMP_ABSOLUTE 4
>> 14 RETURN_VALUE
创建一个空列表(第 0 行),将迭代器参数 (iter(range(1000))
) 压入堆栈(第 2 行),然后开始 for 循环(第 4 行)。循环索引 (_
) 的值存储在本地数组中(第 6 行),None
附加到列表(第 8-10 行),直到循环结束(循环的第 12 行到第 4 行)。
总结一下:
- 第一个版本:乘法;
- 第二个版本:创建一个本地函数,创建一个范围并将迭代器传递给函数;此函数遍历迭代器并逐个附加元素。
第二个版本确实比较慢
注意注意常见的陷阱
>>> A = [[0]] * 3
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0, 1], [0, 1]]
但是:
>>> A = [[0] for _ in range(3)]
>>> A
[[0], [0], [0]]
>>> A[0].append(1)
>>> A
[[0, 1], [0], [0]]
如果您想知道为什么,请查看上面的字节码。