Python 在iterable类上循环的开销
我在摆弄Python的生成器和iterable类,只是为了好玩。基本上,我想测试一些我从来都不太确定的东西:python中的类有一些显著的开销,如果可以的话,最好依赖于实现Python 在iterable类上循环的开销,python,performance,benchmarking,Python,Performance,Benchmarking,我在摆弄Python的生成器和iterable类,只是为了好玩。基本上,我想测试一些我从来都不太确定的东西:python中的类有一些显著的开销,如果可以的话,最好依赖于实现yield的方法,而不是实现迭代器协议的类 我在谷歌上找不到关于这个主题的令人满意的解释,所以我决定自己用这两个简单的脚本来测试它们:func_iter.py和class_iter.py 下面是func\u iter.py: #!/usr/bin/env python import time x = 0 def cre
yield
的方法,而不是实现迭代器协议的类
我在谷歌上找不到关于这个主题的令人满意的解释,所以我决定自己用这两个简单的脚本来测试它们:func_iter.py
和class_iter.py
下面是func\u iter.py
:
#!/usr/bin/env python
import time
x = 0
def create_generator(num):
mylist = range(num)
for i in mylist:
yield i
t = time.time()
gen = create_generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
#!/usr/bin/env python
import time
x = 0
class Generator(object):
def __init__(self, num):
self.start = 0
self.end = num
def __iter__(self):
return self
def next(self):
if self.start == self.end:
raise StopIteration
else:
self.start = self.start + 1
return self.start
t = time.time()
gen = Generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
下面是class\u iter.py
:
#!/usr/bin/env python
import time
x = 0
def create_generator(num):
mylist = range(num)
for i in mylist:
yield i
t = time.time()
gen = create_generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
#!/usr/bin/env python
import time
x = 0
class Generator(object):
def __init__(self, num):
self.start = 0
self.end = num
def __iter__(self):
return self
def next(self):
if self.start == self.end:
raise StopIteration
else:
self.start = self.start + 1
return self.start
t = time.time()
gen = Generator(100000)
for i in gen:
x = x + i
print "%.3f" % (time.time() - t)
然后,我在bash中使用这个函数运行了10次(例如,对于class\u iter.py
):
以下是每种设备的平均运行时间:
class_iter.py: 0.0864
func_iter.py: 0.0307
现在,我的问题是:
class_iter.py
运行的时间几乎是func_iter.py
的三倍编辑:根据Dacav的建议,我还尝试使用
xrange
而不是range
运行func\u iter.py
。这将使其平均运行时间减少到0.0263秒。如果您使用python,很有可能您不以软件性能为目标,但您更关心开发的快速性和敏捷性
话虽如此,我认为比较方法是相当公平的,只要您的代码足够聪明,能够避免对一个解决方案的偏见
例如,基于
yield
的版本的一个可能改进是删除range
功能,并改用xrange
功能。不同之处(在Python2.x中)在于range
构建了一个值列表(因此它必须在内存中为它分配空间),而xrange
构建了一个基于给定值的iterable对象。类版本花费大量时间访问自己的变量。每个self。无论什么都会消耗周期。如果将\uuuu iter\uuuu
定义为生成器,并尽量减少实例变量的使用,则类和函数版本之间的差异可以忽略不计:
setup = """
def create_generator(num):
mylist = range(num)
for i in mylist:
yield i
class Generator(object):
def __init__(self, num):
self.start = 0
self.end = num
def __iter__(self):
return self
def next(self):
if self.start == self.end:
raise StopIteration
else:
self.start = self.start + 1
return self.start
class Generator2(object):
def __init__(self, num):
self.mylist = range(num)
def __iter__(self):
for i in self.mylist:
yield i
"""
import timeit
print timeit.timeit('for p in create_generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator(1000):p', setup, number=1000)
print timeit.timeit('for p in Generator2(1000):p', setup, number=1000)
结果:
0.158941984177
0.696810007095
0.160784959793
因此,第二个生成器类几乎与函数版本一样快
请注意,示例中的Generator
和Generator2
并不完全等效,有时您不能简单地用生成器替换“普通”迭代器(例如封送)。您似乎完全正确,并且您的比较是公平的。当您仅比较开销时,支持迭代器协议的类将比生成器函数慢
然而,在现实世界中,如果代码复杂到足以证明类的合理性,那么算法的运行时间将使开销相形见绌,因此它与程序的运行时间完全无关
你在担心这里的微观优化。你不应该。专注于编写好的、可读的代码,并为工作使用正确的算法。在类版本中用于属性查找和方法调用的时间不会成为您的瓶颈。谢谢!我刚刚试过这个,现在func_iter.py
的平均时间减少到了0.0263。我认为这不是他想要测试的。这里比较的是一个生成器和一个生成器,而不是一个生成器和迭代器协议。是的,该类仍然是可编辑的,但是(例如)您不能pickle它的状态,因为该状态是一个不是该类成员的生成器。已确认!它可能还要慢0.002秒~可以安全地假设这种差异是由于类实例化所需的时间造成的吗?@bow:是的,类实例化+访问\uuuuu iter\uuu
中的实例变量。如果你想知道幕后到底发生了什么,试试dis
模块。开销也(主要)来自函数调用。对于代码中不使用生成器的每次迭代,Python都必须调用next()方法,该方法必须创建函数的框架和局部范围。这需要时间。生成器也有一个框架和局部作用域,但它们只创建一次,并在每次迭代中重复使用。@mdob:是的,这是正确的。Generator2是一个Iterable,其\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。我只是对一些我很久以前就想过的东西感到好奇(但从未真正证明过)~我相信你知道打破神话很有趣:D.@bow我想说你问错了问题。在合理范围内,速度差是多少并不重要。重要的是选择使代码更好的方法。你是对的,一个比较慢,但你应该考虑一下这一点是错的。@bow还值得注意的是,这是一个解决实际问题的网站,而不是理论问题的网站(见FAQ),因此你肯定会得到至少一些解决问题的答案,就好像它不仅仅是学术问题一样。