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

我正在优化一些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): 

即使循环内容相同。

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

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

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

首先,一些观察:

  • 这是一个微不足道的操作的5%。 这很重要。
  • 本地加减操作码的速度是无关紧要的。 这是在噪声层,完全由字节码评估。 这是谈论一个或两个数千人的原生指令。
  • 字节码生成完全相同数量的指令; 唯一的区别是INPLACE_ADDINPLACE_SUBTRACT和+1 vs -1。

看着Python的来源,我可以猜测。 这在PyEval_EvalFrameEx的ceval.c中PyEval_EvalFrameExINPLACE_ADD有一个额外的代码块来处理string连接。 该块不存在于INPLACE_SUBTRACT ,因为您不能减去string。 这意味着INPLACE_ADD包含更多的本地代码。 根据编译器如何生成代码,这个额外的代码可能与INPLACE_ADD代码的其余部分内联,这意味着添加操作可能比减法操作更难。 这可能会导致额外的L2caching命中,这可能会导致显着的性能差异。

这很大程度上取决于你所在的系统(不同的处理器有不同的caching和caching体系结构),使用的编译器,包括特定的版本和编译选项(不同的编译器将决定不同的代码位于关键这决定了汇编代码是如何汇集在一起​​的),等等。

另外,在Python 3.0.1(+:15.66, – :16.71); 毫无疑问,这个关键function已经改变了很多。

 $ 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 

看起来你有一些测量偏差

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

实际测量你的程序性能是无可替代的。 这样做的荣誉; 回答为什么无疑需要深入研究Python的实现,在这种情况下。

使用像Java,Python和.NET这样的字节编译语言,在一台机器上测量性能还不够。 VM版本,本地代码翻译实现,特定于CPU的优化等等之间的差异会使这类问题变得更加棘手。

“第二个循环速度可靠……”

那就是你的解释。 重新sorting你的脚本,所以减法testing先定时, 然后加法,突然加法再次变成更快的操作:

 -= 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 

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

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

  2. Python 2.X的range()构build一个列表。 如果你正在使用Python 2.x,用xrangereplacerange ,看看会发生什么。

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

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

这里比较一般的编程教训是直觉是预测计算机代码运行时性能的一个不好的指导。

人们可以推论algorithm的复杂性,假设编译器优化,估计caching性能等等。 但是,由于这些事情可以以不重要的方式进行交互,因此确定特定代码将要运行得多快的唯一方法就是在目标环境中对其进行基准testing(正如您已经正确完成的那样)。

在Python 2.5中,最大的问题是使用范围,它将分配一个大的列表来遍历它。 使用xrange时,以秒为单位,对我来说快一点。 (不确定范围是否已经成为Python 3中的一个生成器)

你的实验是错误的。 这个实验的devise方法是编写2个不同的程序 – 1个加法,1个减法。 他们应该是完全一样的,并在相同条件下运行数据。 那么你需要平均运行(至less几千),但你需要一个统计学家告诉你一个合适的数字。

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

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

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

  • 运行你的代码validation你的声明: – =需要不断的更less的时间; 平均3.6%
  • 但是,运行我的代码与您的实验结果相矛盾:+ =平均(并非总是)要花费0.5%的时间。

为了显示所有的结果,我已经把networking线上:

  • 您的评价: http : //bayimg.com/kadAeaAcN
  • 我的评价: http : //bayimg.com/KadaAaAcN

所以,我认为你的实验有一个偏见,这是有意义的。

最后这里是我的代码:

 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) 

向后运行的循环速度更快,因为计算机比较数字是否等于0比较容易。