Python 3的枚举速度是否比Python 2慢?

时间:2014-05-04 05:57:42

标签: python performance loops python-2.7 python-3.x

Python 3的最小循环枚举速度似乎比Python 2要慢得多,而新版本的Python 3似乎越来越差。

我的64位Windows机器上安装了Python 2.7.6,Python 3.3.3和Python 3.4.0,(Intel i7-2700K - 3.5 GHz),每个都有32位和64位版本安装了Python。虽然对于给定版本,在存储器访问限制内,32位和64位之间的执行速度没有显着差异,但不同版本级别之间存在非常显着的差异。我会让时间结果说明如下:

C:\**Python34_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **900 msec** per loop

C:\**Python33_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **820 msec** per loop

C:\**Python27_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: **480 msec** per loop

自Python 3"范围"与Python 2" range"不同,并且在功能上与Python 2" xrange"相同,我还按如下方式计时:

C:\**Python27_64**\python -mtimeit -n 5 -r 2 -s"cnt = 0" "for i in **xrange**(10000000): cnt += 1"
5 loops, best of 2: **320 msec** per loop

可以很容易地看到版本3.3几乎是版本2.7的两倍,而Python 3.4比再版本慢了大约10%。

我的问题:是否有一个环境选项或设置可以纠正这个问题,或者只是效率低下的代码或者解释器为Python 3版本做了更多的事情?


答案似乎是Python 3使用"无限精度"曾经被称为" long"在Python 2.x中它的默认值为" int"类型没有任何选项使用Python 2固定位长" int"并且它正在处理这些可变长度的" int"这需要花费额外的时间,如下面的答案和评论所述。

可能是Python 3.4比Python 3.3稍慢,因为内存分配的变化支持同步,这会稍微减慢内存分配/释放,这可能是当前版本的#34; long&#34的主要原因;处理速度较慢。

2 个答案:

答案 0 :(得分:26)

我从这个问题中学到的东西的总结回答可能对那些和我一样有疑问的人有所帮助:

  1. 减速的原因是Python 3.x中的所有整数变量现在都是"无限精度"作为过去被称为" long"在Python 2.x中,但现在是由PEP 237决定的唯一整数类型。根据该文件,"短"具有基础架构的位深度的整数不再存在(或仅在内部)。

  2. 旧的"短"变量操作可以合理地运行,因为它们可以直接使用底层机器代码操作并优化新的" int"对象,因为它们总是具有相同的大小。

  3. " long" type目前仅由在内存中分配的类对象表示,因为它可能超过给定的固定长度寄存器/内存位置的位深度;由于这些对象表示可能会因各种操作而增大或缩小,因此具有可变大小,因此无法为它们提供固定的内存分配并留在那里。

  4. 这些" long"类型(当前)不使用整机结构字大小但保留一点(通常是符号位)进行溢出检查,因此"无限精度长"被划分(当前)为15位/ 30位切片"数字"适用于32位/ 64位架构。

  5. 这些"长期"的许多常见用途整数不会需要多个(或者32位架构可能需要两个)"数字"作为一个"数字"的范围对于64位/ 32位架构,分别约为10亿/ 32768。

  6. ' C'代码相当有效地做一两个数字"操作,所以性能成本超过简单的"短"与运行字节码解释器循环所需的时间相比,对于许多常见用途而言,整数并不高实际计算

  7. 最大的性能影响可能是常量内存分配/解除分配,每对循环整数操作一对非常昂贵,尤其是当Python支持多线程时使用同步锁(这可能是Python 3.4比3.3差的原因)。

  8. 目前,实施始终确保足够的数字"通过分配一个额外的"数字"超过"数字的实际大小"用于最大操作数,如果有可能"增长",进行操作(可能会或可能不会实际使用额外的"数字"),然后将结果标准化长度以计算"数字的实际数量"使用过,实际上可能保持不变(或者可能"收缩"对于某些操作);这是通过减少" long"中的大小计数来完成的。没有新分配的结构可能会浪费一个数字"内存空间,但节省了另一个分配/释放周期的性能成本。

  9. 希望提高性能:对于许多操作,可以预测操作是否会导致"增长"或者不是 - 例如,对于添加者,只需要查看最高有效位(MSB' s),如果两个MSB都为零,则操作不能增长,这将是是许多循环/计数器操作的情况;减法赢了"#34;成长"取决于两个操作数的符号和MSB;左移只会"成长"如果MSB是一个;等等。

  10. 对于声明类似于" cnt + = 1" /" i + = step"等等(为许多用例打开了操作的可能性),"到位"可以调用操作的版本,这将执行适当的快速检查,并且仅在"增长"时才分配新对象。是必要的,否则做代替第一个操作数的操作。复杂的是编译器需要生成这些"就地"然而,已经完成的字节代码,具有适当的特殊"就地操作"产生的字节代码,只是当前的字节代码解释器将它们引导到如上所述的通常版本,因为它们尚未实现(表中零&#d; / null值)。

  11. 很可能所有必须要做的就是这些"就地操作的编写版本"并将它们填入" long"带有字节码解释器的方法表,如果它们存在,它们已经找到并运行它们,或者对表进行微小的更改,使它只需要调用它们。

  12. 请注意,浮点数始终是相同的大小,因此可以进行相同的改进,尽管浮点数在备用位置块中分配以提高效率;对于" long"来说,因为他们占用了不同的内存量,所以要难得多。

    另请注意,这会破坏长期"(以及可选择的浮动)的不变性,这就是为什么没有定义的内部运营商,但事实是他们被视为可变只是因为这些特殊情况不会影响外部世界,因为它永远不会意识到有时某个给定对象具有与旧值相同的地址(只要等式比较看内容而不仅仅是对象地址)。

    我相信通过避免这些常见用例的内存分配/分配,Python 3.x的性能将非常接近Python 2.7。

    我在这里学到的很多东西来自Python trunk 'C' source file for the "long" object


    EDIT_ADD:哎呀,忘记了如果变量有时是可变的,那么对局部变量的闭包不起作用或者在没有重大变化的情况下无法工作,这意味着上述就地操作会"打破"关闭。似乎一个更好的解决方案是提前进行备用分配快速工作,以及#34; s就像以前用于短整数一样,并且用于浮点数。 ,即使仅适用于" long"大小不会改变(这涵盖了大部分时间,例如根据问题的循环和计数器)。这样做应该意味着代码的运行速度不会比Python 2慢得多。

答案 1 :(得分:18)

差异是由于用int类型替换long类型。显然,长整数运算会变慢,因为long运算更复杂。

如果通过将cnt设置为0L来强制python2使用long,则差异就会消失:

$python2 -mtimeit -n5 -r2 -s"cnt=0L" "for i in range(10000000): cnt += 1L"
5 loops, best of 2: 1.1 sec per loop
$python3 -mtimeit -n5 -r2 -s"cnt=0" "for i in range(10000000): cnt += 1"
5 loops, best of 2: 686 msec per loop
$python2 -mtimeit -n5 -r2 -s"cnt=0L" "for i in xrange(10000000): cnt += 1L"
5 loops, best of 2: 714 msec per loop

正如你在我的机器上看到的那样,python3.4使用range比使用xrange时使用long更快。使用python {2} xrange的最后一个基准测试表明,这种情况的差异很小。

我没有安装python3.3,所以我无法在3.3和3.4之间进行比较,但据我所知,这两个版本(关于range)之间没有任何重大变化,所以时间应该大致相同。如果您发现重大差异,请尝试使用dis模块检查生成的字节码。有关内存分配器(PEP 445)的更改,但我不知道默认内存分配器是否已被修改,以及性能方面的后果。