2016-02-04 41 views
3

我的印象是使用总和构造比运行for循环要快得多。然而,在下面的代码中,for循环实际运行速度更快:Python中“总和”理解的速度

import time 

Score = [[3,4,5,6,7,8] for i in range(40)] 

a=[0,1,2,3,4,5,4,5,2,1,3,0,5,1,0,3,4,2,2,4,4,5,1,2,5,4,3,2,0,1,1,0,2,0,0,0,1,3,2,1] 

def ver1(): 
    for i in range(100000): 
     total = 0 
     for j in range(40): 
      total+=Score[j][a[j]] 
    print (total) 

def ver2(): 
    for i in range(100000): 
     total = sum(Score[j][a[j]] for j in range(40)) 
    print (total) 


t0 = time.time() 
ver1() 
t1 = time.time() 
ver2() 
t2 = time.time() 

print("Version 1 time: ", t1-t0) 
print("Version 2 time: ", t2-t1) 

输出是:

208 
208 
Version 1 time: 0.9300529956817627 
Version 2 time: 1.066061019897461 

难道我做错了什么?有没有办法做得更快?

(请注意,这只是一个演示中,我设置了,在我的实际应用中的成绩将不会以这种方式重复)

一些附加信息:这是关于Python 3.4.4 64位运行,在Windows 7 64位上,在i7上。

+0

[this question](http:// stackoverflow。com/questions/24578896/python-built-in-sum-function-vs-for-loop-performance)说'sum'应该比'for'循环快得多。 – Barmar

+0

我认为你的瓶颈是列表理解,而不是加法。 – Barmar

+1

@Barmar在这两个函数中都没有列表理解。为什么一个*生成器理解*是一个瓶颈,因为它和for循环完全相同?我认为这可能只是函数调用'sum'的开销,因为范围非常小... – L3viathan

回答

1

由于j被迭代两个列表,我想我会看看是否拉链工作更好:

def ver3(): 
    for i in range(100000): 
     total = sum(s[i] for s,i in zip(Score,a)) 
    print (total) 

在此的Py2运行速度比2.0版本要慢30%左右,但对PY3约20%的速度如果我将zip更改为izip(从itertools导入),则会将时间缩短到版本1和版本2之间。

+1

哎呀,如果你想变得聪明并且在'sum':'from operator import getitem','total = sum(map(getitem,Score,a))'''''可能会做得更好(在Py2上,使用'itertools.imap'来避免中间'list')。 – ShadowRanger

2

这似乎取决于系统,可能是python版本。在我的系统,不同的是约13%:

python sum.py 
208 
208 
('Version 1 time: ', 0.6371259689331055) 
('Version 2 time: ', 0.7342419624328613) 

两个版本没有测量sum与手动循环,因为循环的“身体”是不相同的。 ver2做了更多的工作,因为它创建了100000次生成器表达式,而ver1的循环体几乎是微不足道的,但它为每次迭代创建了一个包含40个元素的列表。你可以改变的例子是相同的,然后你看到的sum效果:

def ver1(): 
    r = [Score[j][a[j]] for j in range(40)] 
    for i in xrange(100000): 
     total = 0 
     for j in r: 
      total+=j 
    print (total) 

def ver2(): 
    r = [Score[j][a[j]] for j in xrange(40)] 
    for i in xrange(100000): 
     total = sum(r) 
    print (total) 

我搬到一切从内循环体的进出sum通话,以确保我们只测量手工制作的循环的开销。使用xrange而不是range可进一步改善整体运行时间,但这适用于两个版本,因此不会更改比较。我的系统上修改后的代码的结果是:

python sum.py 
208 
208 
('Version 1 time: ', 0.2034609317779541) 
('Version 2 time: ', 0.04234910011291504) 

ver2ver1快5倍。这是使用sum而不是手工制作的循环的纯粹性能增益。

ShadowRanger's comment on the question about lookups启发,我已修改例子来比较的原代码,并检查是否结合的符号的查找:

def gen(s,b): 
    for j in xrange(40): 
     yield s[j][b[j]] 

def ver2(): 
    for i in range(100000): 
     total = sum(gen(Score, a)) 
    print (total) 

创建局部地结合Scorea防止昂贵查找一个小的自定义发生器在父范围内。执行此操作:

python sum.py 
208 
208 
('Version 1 time: ', 0.6167840957641602) 
('Version 2 time: ', 0.6198039054870605) 

单独的符号查找占运行时间的约12%。

+0

我不认为这是一个公平的比较。关键是找到计算总和的最快方法,通过从循环中删除中间列表,您基本上已预先计算了总和的一部分,并且只显示在平面列表上使用“总和”比迭代快。 – CaptainCodeman

+1

@CaptainCodeman如果你想比较'sum'和hadn-written循环的速度,你应该把所有的东西都拿出来。否则,你正在比较苹果和橘子。否则,我会强烈地争辩说,你的ver2代码只是次优。 – Jens

+0

如果你只是想要一个平面数组,那么肯定,“总和”会更快,但这从来没有问题。这是测试它可能会出现一个更复杂的应用程序。在实践中无法使阵列变平。 – CaptainCodeman