为什么 for _ in range(n) 比 for _ in [""]*n 慢?
Why is for _ in range(n) slower than for _ in [""]*n?
测试 for _ in range(n)
的替代方案(执行某个动作 n
次,即使该动作不依赖于 n
的值)我注意到还有另一种表述这种模式更快,for _ in [""] * n
.
例如:
timeit('for _ in range(10^1000): pass', number=1000000)
returns 16.4 秒;
而
timeit('for _ in [""]*(10^1000): pass', number=1000000)
需要 10.7 秒。
为什么 [""] * 10^1000
在 Python 3 中比 range(10^1000)
快这么多?
所有测试使用 Python 3.3
您的问题是喂食不正确 timeit
。
您需要提供 timeit
个包含 Python 个语句的字符串。如果你这样做
stmt = 'for _ in ['']*100: pass'
查看stmt
的值。方括号内的引号字符与字符串定界符匹配,因此它们被 Python 解释为字符串定界符。由于 Python 连接相邻的字符串文字,您将看到您真正拥有的与 'for _ in [' + ']*100: pass'
相同,这给您 'for _ in []*100: pass'
.
所以你的 "super-fast" 循环只是在空列表上循环,而不是 100 个元素的列表。试试你的测试,例如,
stmt = 'for _ in [""]*100: pass'
当遍历 range()
时,生成 0 到 n
之间所有整数的对象;这需要(少量)时间,即使 small integers having been cached.
另一方面,[None] * n
上的循环会生成对 1 个对象的 n
引用,并且创建该列表的速度要快一些。
然而,range()
对象使用 far 更少的内存, 和 启动时更具可读性,这就是为什么人们更喜欢使用它。大多数代码不必从性能中榨取最后一滴。
如果您需要这样的速度,您可以使用不占用内存的自定义迭代器,使用 itertools.repeat()
和第二个参数:
from itertools import repeat
for _ in repeat(None, n):
至于你的计时测试,有一些问题。
首先,你的['']*n
计时循环出错了;您没有嵌入两个引号,而是连接了两个字符串并生成了一个 空列表 :
>>> '['']*n'
'[]*n'
>>> []*100
[]
这在迭代中将是无与伦比的,因为您迭代了 0 次。
你也没有用大数字; ^
是二元异或运算符,不是幂运算符:
>>> 10^1000
994
这意味着您的测试错过了创建大空值列表所需的时间。
使用更好的数字和 None
给你:
>>> from timeit import timeit
>>> 10 ** 6
1000000
>>> timeit("for _ in range(10 ** 6): pass", number=100)
3.0651066239806823
>>> timeit("for _ in [None] * (10 ** 6): pass", number=100)
1.9346517859958112
>>> timeit("for _ in repeat(None, 10 ** 6): pass", 'from itertools import repeat', number=100)
1.4315521717071533
测试 for _ in range(n)
的替代方案(执行某个动作 n
次,即使该动作不依赖于 n
的值)我注意到还有另一种表述这种模式更快,for _ in [""] * n
.
例如:
timeit('for _ in range(10^1000): pass', number=1000000)
returns 16.4 秒;
而
timeit('for _ in [""]*(10^1000): pass', number=1000000)
需要 10.7 秒。
为什么 [""] * 10^1000
在 Python 3 中比 range(10^1000)
快这么多?
所有测试使用 Python 3.3
您的问题是喂食不正确 timeit
。
您需要提供 timeit
个包含 Python 个语句的字符串。如果你这样做
stmt = 'for _ in ['']*100: pass'
查看stmt
的值。方括号内的引号字符与字符串定界符匹配,因此它们被 Python 解释为字符串定界符。由于 Python 连接相邻的字符串文字,您将看到您真正拥有的与 'for _ in [' + ']*100: pass'
相同,这给您 'for _ in []*100: pass'
.
所以你的 "super-fast" 循环只是在空列表上循环,而不是 100 个元素的列表。试试你的测试,例如,
stmt = 'for _ in [""]*100: pass'
当遍历 range()
时,生成 0 到 n
之间所有整数的对象;这需要(少量)时间,即使 small integers having been cached.
另一方面,[None] * n
上的循环会生成对 1 个对象的 n
引用,并且创建该列表的速度要快一些。
然而,range()
对象使用 far 更少的内存, 和 启动时更具可读性,这就是为什么人们更喜欢使用它。大多数代码不必从性能中榨取最后一滴。
如果您需要这样的速度,您可以使用不占用内存的自定义迭代器,使用 itertools.repeat()
和第二个参数:
from itertools import repeat
for _ in repeat(None, n):
至于你的计时测试,有一些问题。
首先,你的['']*n
计时循环出错了;您没有嵌入两个引号,而是连接了两个字符串并生成了一个 空列表 :
>>> '['']*n'
'[]*n'
>>> []*100
[]
这在迭代中将是无与伦比的,因为您迭代了 0 次。
你也没有用大数字; ^
是二元异或运算符,不是幂运算符:
>>> 10^1000
994
这意味着您的测试错过了创建大空值列表所需的时间。
使用更好的数字和 None
给你:
>>> from timeit import timeit
>>> 10 ** 6
1000000
>>> timeit("for _ in range(10 ** 6): pass", number=100)
3.0651066239806823
>>> timeit("for _ in [None] * (10 ** 6): pass", number=100)
1.9346517859958112
>>> timeit("for _ in repeat(None, 10 ** 6): pass", 'from itertools import repeat', number=100)
1.4315521717071533