为什么减法比Python中的加法更快?

时间:2009-09-08 22:02:00

标签: python performance addition subtraction

我正在优化一些Python代码,并尝试了以下实验:

import time

start = time.clock()
x = 0
for i in range(10000000):
    x += 1
end = time.clock()

print '+=',end-start

start = time.clock()
x = 0
for i in range(10000000):
    x -= -1
end = time.clock()

print '-=',end-start

第二个循环可靠地更快,从胡须到10%,取决于我运行它的系统。我已经尝试改变循环的顺序,执行次数等,它似乎仍然有用。

陌生人,

for i in range(10000000, 0, -1):

(即向后运行循环)比

更快
for i in range(10000000):

即使循环内容相同。

这给了什么,是否有更一般的编程课程?

10 个答案:

答案 0 :(得分:74)

我可以在Q6600上重现这一点(Python 2.6.2);将范围增加到100000000:

('+=', 11.370000000000001)
('-=', 10.769999999999998)

首先,一些观察结果:

  • 这是一个微不足道的操作的5%。这很重要。
  • 原生加法和减法操作码的速度无关紧要。它位于本底噪声中,与字节码评估完全相形见绌。这是关于成千上万的一两条原生指令。
  • 字节码生成完全相同数量的指令;唯一的区别是INPLACE_ADDINPLACE_SUBTRACT和+1对-1。

看看Python源码,我可以猜一猜。这在PyEval_EvalFrameEx中的ceval.c中处理。 INPLACE_ADD有一个重要的额外代码块,用于处理字符串连接。 INPLACE_SUBTRACT中不存在该块,因为您无法减去字符串。这意味着INPLACE_ADD包含更多本机代码。依赖于(严重!)编译器如何生成代码,这个额外的代码可能与INPLACE_ADD代码的其余部分内联,这意味着添加可以比减法更难命中达指令缓存。这个可能导致额外的L2缓存命中,这可能会导致显着的性能差异。

这在很大程度上取决于您所使用的系统(不同处理器具有不同数量的缓存和缓存体系结构),正在使用的编译器,包括特定版本和编译选项(不同的编译器将决定哪些代码位是不同的在关键路径上,它确定如何将汇编代码集中在一起),等等。

此外,Python 3.0.1(+:15.66, - :16.71)中的差异相反;毫无疑问,这个关键功能已经发生了很大变化。

答案 1 :(得分:13)

$ python -m timeit -s "x=0" "x+=1"
10000000 loops, best of 3: 0.151 usec per loop
$ python -m timeit -s "x=0" "x-=-1"
10000000 loops, best of 3: 0.154 usec per loop

看起来你有一些measurement bias

答案 2 :(得分:7)

我认为“一般编程课程”是真的难以预测,仅通过查看源代码,哪个语句序列将是最快的。各级程序员经常被这种“直观”优化所困扰。你认为你所知道的可能不一定是真的。

实际上测量你的程序性能根本无法替代。感谢这样做;回答为什么毫无疑问需要深入研究Python的实现,在这种情况下。

使用Java,Python和.NET等字节编译语言,仅在一台计算机上测量性能甚至还不够。 VM版本,本机代码转换实现,CPU特定优化等之间的差异将使这类问题变得更加难以回答。

答案 3 :(得分:5)

  

“第二个循环可靠得快......”

那是你的解释。重新排序脚本,以便先减去测试时间,然后添加,然后突然添加成为更快的操作:

-= 3.05
+= 2.84

显然,脚本的后半部分会发生一些事情,使其更快。我的猜测是第一次调用range()的速度较慢,因为python需要为这么长的列表分配足够的内存,但它可以重新使用该内存进行第二次调用range()

import time
start = time.clock()
x = range(10000000)
end = time.clock()
del x
print 'first range()',end-start
start = time.clock()
x = range(10000000)
end = time.clock()
print 'second range()',end-start

此脚本的一些运行表明,第一个range()所需的额外时间几乎可以解释上面所见的'+ ='和' - ='之间的所有时差:

first range() 0.4
second range() 0.23

答案 4 :(得分:4)

当问一个问题说出你正在使用什么平台和什么版本的Python时,这总是一个好主意。有时候没关系。这不是那个时代之一:

  1. time.clock()仅适用于Windows。丢弃您自己的测量代码并使用-m timeit,如pixelbeat的答案所示。

  2. Python 2.X的range()构建一个列表。如果您使用的是Python 2.x,请将range替换为xrange,看看会发生什么。

  3. Python 3.X的int是Python2.X的long

答案 5 :(得分:2)

这里有更一般的编程课吗?

这里更一般的编程课程是,在预测计算机代码的运行时性能时,直觉是一个糟糕的指南。

可以推断算法的复杂性,编译器优化的假设,估计缓存性能等等。但是,由于这些东西可以以非平凡的方式进行交互,因此确定特定代码片段的速度的唯一方法是在目标环境中对其进行基准测试(正如您所理解的那样)。

答案 6 :(得分:0)

使用Python 2.5时,最大的问题是使用range,它会分配一个大的迭代列表。当使用xrange时,无论哪个都做到第二,对我来说要快一点。 (不确定范围是否已成为Python 3中的生成器。)

答案 7 :(得分:0)

您的实验有问题。应该设计这个实验的方法是编写2个不同的程序 - 1个用于加法,1个用于减法。它们应该完全相同,并在相同条件下运行,并将数据存入文件。然后你需要平均运行(至少几千),但你需要一个统计学家来告诉你一个合适的数字。

如果你想分析加法,减法和循环的不同方法,那么每个方法都应该是一个单独的程序。

实验错误可能源于处理器的热量和CPU上的其他活动,所以我会以各种模式执行运行......

答案 8 :(得分:0)

这将是非凡的,所以我已经彻底评估了你的代码并设置了expiriment,因为我会发现它更多正确(循环外的所有声明和函数调用)。这两个版本都运行了五次。

  • 运行代码验证了您的声明: - =花费的时间越来越少;平均为3.6%
  • 但是,运行我的代码与您的实验结果相矛盾: + =平均(不总是)减少0.5%的时间。

要显示我在线绘制的所有结果:

因此,我得出结论,你的实验有偏见,而且意义重大。

最后这是我的代码:

import time

addtimes = [0.] * 100
subtracttimes = [0.] * 100

range100 = range(100)
range10000000 = range(10000000)

j = 0
i = 0
x = 0
start = 0.


for j in range100:
 start = time.clock()
 x = 0
 for i in range10000000:
  x += 1
 addtimes[j] = time.clock() - start

for j in range100:
 start = time.clock()
 x = 0
 for i in range10000000:
  x -= -1
 subtracttimes[j] = time.clock() - start

print '+=', sum(addtimes)
print '-=', sum(subtracttimes)

答案 9 :(得分:-1)

向后运行循环更快,因为如果数字等于0,计算机可以更容易地进行比较。