列表索引效率(Python 2与Python 3比较)

27

在回答另一个问题时,我建议使用timeit测试用正整数和负整数索引列表之间的差异。以下是代码:

import timeit
t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=10000000)
print (t)
t=timeit.timeit('mylist[-1]',setup='mylist=list(range(100))',number=10000000)
print (t)

我使用Python 2.6运行了这段代码:

$ python2.6 test.py
0.587687015533
0.586369991302

然后我使用 Python 3.2 运行它:

$ python3.2 test.py
0.9212150573730469
1.0225799083709717

然后我挠了挠头,做了一些谷歌搜索,并决定在这里发布这些观察结果。

操作系统:OS-X(10.5.8)--Intel Core2Duo

对我来说,这似乎是一个非常显著的差异(超过1.5倍的因素)。有人知道为什么Python3如此缓慢吗?尤其是对于这样一个常见的操作?

编辑

我在我的Ubuntu Linux桌面上运行了相同的代码(Intel i7),使用Python2.6和Python 3.2获得了可比较的结果。看起来这是一个与操作系统(或处理器)有关的问题(其他用户在Linux机器上看到了相同的行为--请参见评论)。

编辑2

其中一个答案要求提供启动横幅,所以在这里:

Python 2.6.4 (r264:75821M, Oct 27 2009, 19:48:32) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin

并且:

Python 3.2 (r32:88452, Feb 20 2011, 10:19:59) 
[GCC 4.0.1 (Apple Inc. build 5493)] on darwin

更新

我刚刚从http://www.python.org/download/下载并安装了最新版本的Python 2.7.3和Python 3.2.3。

在这两种情况下,我选择了

"Python x.x.3 Mac OS X 32-bit i386/PPC Installer (for Mac OS X 10.3 through 10.6 [2])"

因为我使用的是OS X 10.5。以下是新的计时结果(多次试验结果相当一致):

Python 2.7

$python2.7 test.py
0.577006101608
0.590042829514

python 3.2.3

$python3.2 test.py
0.8882801532745361
1.034242868423462

尝试在Python 2.x中使用xrange() - Ashwini Chaudhary
2
@AshwiniChaudhary -- 那些东西都在setup中,不会计时。 xrangerange无关。 - mgilson
1
供参考:这里有一个回答讨论了Python3.0中没有特殊处理小整数的问题,也许在Python3.2中仍然缺失。 - Otto Allmendinger
1
@OttoAllmendinger -- 这与一些答案一致(OS-X与Linux之间的差异更大)。你可能是对的。 - mgilson
1
@mgilson 我在我的Linux机器上得到的结果与你大致相同且一致。 - Otto Allmendinger
显示剩余10条评论
3个回答

24

这似乎是Python 3.2某些版本的产物。目前最好的假设是,所有32位Intel版本都会出现减速,但64位版本不会。继续阅读以了解更多细节。

你并没有运行足够的测试来确定任何事情。重复你的测试很多次后,我得到了在相同测试中范围从0.31到0.54的值,这是一个巨大的变化。

所以,我使用了10x的数量和repeat=10,在许多不同的Python2和Python3安装中运行了你的测试。去掉最高和最低值,对其他8个结果求平均,并除以10(得到与你测试相等的数字),这是我看到的结果:

 1. 0.52/0.53 Lion 2.6
 2. 0.49/0.50 Lion 2.7
 3. 0.48/0.48 MacPorts 2.7
 4. 0.39/0.49 MacPorts 3.2
 5. 0.39/0.48 HomeBrew 3.2

所以,看起来对于[99],3.2实际上稍微快了一些,而对于[-1],速度大致相同。

然而,在一台10.5的机器上,我得到了以下结果:

 1. 0.98/1.02 MacPorts 2.6
 2. 1.47/1.59 MacPorts 3.2

回到原来的机器(Lion),我以32位模式运行,结果得到了这个:

 1. 0.50/0.48 Homebrew 2.7
 2. 0.75/0.82 Homebrew 3.2
看起来,32位与64位是重要的因素,而不是Leopard vs. Lion、gcc 4.0 vs. gcc 4.2或clang、硬件差异等。测试64位构建在Leopard下使用不同的编译器等可能会有所帮助,但不幸的是我的Leopard盒子是第一代Intel Mini(带有32位Core Solo CPU),所以我无法进行该测试。
作为进一步的间接证据,我在Lion盒子上运行了一系列其他快速测试,看起来32位3.2比2.x慢约50%,而64位3.2可能比2.x稍快。但如果我们真的想支持这一点,需要有人选择并运行真正的基准套件。
总之,我目前最好的猜测是,在优化3.x分支时,没有人花费太多精力来构建32位i386 Mac。对于他们来说,这实际上是一个合理的选择。
或者,另一种可能性是,他们甚至没有花太多精力在32位i386周期间。这种可能性可能解释了OP在linux box上看到2.x和3.2给出类似结果的原因,而Otto Allmendinger在linux box上看到3.2与2.6相比较慢。但由于他们都没有提到他们是否在运行32位或64位linux,所以很难知道这是否相关。
仍然有许多其他可能性我们没有排除,但这似乎是最好的一个。

两者都应该是32位。OS-X 10.5.8是一个32位的操作系统,所以我无法运行64位版本。 - mgilson
1
首先,这是一个很好的答案(+1),尽管我并不完全相信这是原因。我已经发布了我的两台机器的启动横幅和处理器。据我所知,看起来我从同一个地方获取了两个Python安装程序,并使用相同的编译器进行编译。如果其他人可以使用OS-X 10.5.8重现您的测试,我可能会被说服。(如果没有其他更好的答案出现,我可能仍然会接受这个答案——这是一个很好的答案)。 - mgilson
1
我正在一台10.5机器上安装当前的MacPorts 2.6和3.2,这将需要很长时间...但是在漫长的一小时之后,我会在那里运行测试并查看发生了什么。如果它无法重现您的结果,那应该可以确定您的3.2构建存在某些奇怪的问题,而不是3.2总体存在问题。如果可以重现,就有更多的调查空间。 - abarnert
我刚刚重新从http://www.python.org/download/安装了二进制文件。在这两种情况下(现在是Python 2.7.3与3.2.3),我使用了32位的i386 / PPC版本。尽管时间现在略微接近,但问题仍然存在。我会更新我的发帖内容。 - mgilson
1
好的,没错;如果你想知道为什么它更慢,你需要查看源代码和/或分析解释器和/或浏览Python开发者列表的档案,这些都比我现在愿意付出的努力更多... - abarnert
显示剩余4条评论

4
这里有一段代码,至少部分说明了答案:
$ python
Python 2.7.3 (default, Apr 20 2012, 22:44:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
2.55517697334
>>> t=timeit.timeit('mylist[99L]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
3.89904499054

$ python3
Python 3.2.3 (default, May  3 2012, 15:54:42) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import timeit
>>> t=timeit.timeit('mylist[99]',setup='mylist=list(range(100))',number=50000000)
>>> print (t)
3.9906489849090576

Python3不再有旧的int类型。


非常有趣。使用长整型可以得到与Python 3.x版本非常相似的计时结果。 - mgilson
1
不错。快速搜索显示了一篇关于在Python 3.1中加速长整型的帖子,因为它比2.x整数慢得多,但不幸的是该网站本身无法访问。更加勤奋的搜索可能会找到他们确切的解决方法,以及为什么对32位系统的帮助不大。但我可以想象出很多可能性。例如,使用64位字,小长整型适合一个字的技巧就不需要一次读取一个字符... - abarnert

0

Python 3中的range()函数相当于Python 2中的xrange()函数。如果你想在Python 3代码中模拟Python 2中的range()函数,你必须使用list(range(num))num越大,与原始代码相比观察到的差异就越大。

索引应该独立于列表内存储的内容,因为列表仅存储对目标对象的引用。这些引用是无类型的且都是相同类型的。因此,列表类型是一种同构数据结构 - 技术上而言。索引意味着将索引值转换为起始地址+偏移量。通过最多一次减法计算偏移量非常高效。与其他操作相比,这是非常便宜的额外操作。


6
叹气 -- 我们已经讨论过这个问题了。在timeit中的“setup”部分不会被计时。在我的设置中,我使用list(range(num)),所以正在计时的东西(在两种情况下)是一组数字列表,而不是一个范围对象。我也理解你第二段中提到的内容。问题是为什么在Python 3.x中执行那些操作比Python 2.x慢这么多? - mgilson
@mgilson; +1,你是对的。下次我应该更好地阅读 ;) - pepr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接