python:总和的速度

时间:2016-11-20 19:52:37

标签: python performance

我知道对数字列表求和的最快方法是使用内置函数sum。使用for循环可能比使用reduce更慢。但是,当我尝试它时,事实并非如此。有人可以解释这个结果吗?

import time, random, operator

sample = [random.randrange(10000) for _ in range(1000000)]

def use_for(l):
    acc = 0
    for n in l:
        acc += n
    print acc

def use_lambda(l):
    print reduce(operator.add, l)

print time.time()
use_for(l)
print time.time()
use_lambda(l)
print time.time()

我得到的时间:

1479671513.04
4998734199
1479671513.07
4998734199
1479671513.13

3 个答案:

答案 0 :(得分:4)

让我告诉你如何更系统地做到这一点。首先,您应该使用timeit模块进行基准测试。使用正确有点尴尬,但它更准确。其次,绝对确定你没有做任何其他的工作,而不是你在考试中进行基准测试的工作。特别是,您不应在被测功能中打印任何内容,因为打印物品很昂贵。第三,您应该在范围的长度上测试每个候选函数,然后绘制结果图。第四,你不需要达到一百万个数字才能获得有用的结果。

import csv
import operator
import random
import sys
from functools import partial, reduce
from timeit import timeit

lengths = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000]

samples = [ [random.randrange(10000) for i in range(n)]
            for n in lengths ]

def use_for(l):
    acc = 0
    for n in l: acc += n
    return acc

def use_reduce(l):
    return reduce(operator.add, l)

def use_sum(l):
    return sum(l)

def main():
    with sys.stdout as ofp:
        wr = csv.writer(ofp, lineterminator='\n', quoting=csv.QUOTE_MINIMAL)
        wr.writerow(('len','for loop','reduce','sum'))

        for length, sample in zip(lengths, samples):
            t_for = timeit(partial(use_for, sample), number=1000)
            t_red = timeit(partial(use_reduce, sample), number=1000)
            t_sum = timeit(partial(use_sum, sample), number=1000)
            wr.writerow((length, t_for, t_red, t_sum))

main()

我们运行此测试程序,然后绘制输出。你没有说你是使用Python 2还是3,所以我写了上面的内容来处理它们,我用两种方式测试它。 [编辑:自从另一个回答提到它以来,我现在也测试了PyPy。] 不要担心我和我的问题的细节我正在努力制作情节 - ggplot非常值得学习,但它和它所嵌入的R语言可能相当神秘。

$ python2 sumbench.py > sumbench-2.csv
$ python3 sumbench.py > sumbench-3.csv
$ pypy    sumbench.py > sumbench-P.csv
$ R --quiet
> suppressPackageStartupMessages({ library(reshape2); library(ggplot2); })
> data2 <- melt(read.csv('sumbench-2.csv'), id.var='len')
> data3 <- melt(read.csv('sumbench-3.csv'), id.var='len')
> dataP <- melt(read.csv('sumbench-P.csv'), id.var='len')
> data2$interp <- ordered('CPython 2', levels=c('CPython 2','CPython 3','PyPy'))
> data3$interp <- ordered('CPython 3', levels=c('CPython 2','CPython 3','PyPy'))
> dataP$interp <- ordered('PyPy',      levels=c('CPython 2','CPython 3','PyPy'))
> data <- rbind(data2, data3, dataP)
> colnames(data) <- c("Input length", "Algorithm", "Time (ms)", "Interpreter")
> ggplot(data, aes(x=`Input length`, y=`Time (ms)`,
                   colour=`Algorithm`, linetype=`Algorithm`)) +
      facet_grid(.~`Interpreter`) + geom_line() +
      theme_grey(base_size=9) +
      theme(legend.position=c(0.01,0.98), legend.justification=c(0,1))

Linear plot of all three algorithms' performance on three different Python interpreters

这清楚地表明使用reduce确实比for循环慢,但sum要快得多。它也清楚地表明CPython 3.5在这方面比2.7更慢,这是令人悲伤但预期的。 PyPy不仅比其中任何一个都快5倍,而且所有三种算法都表现得同样出色!当你在这种代码中抛出一个真正的优化编译器时会发生什么。 (PyPy比CPython的sum()内在更快,因为它可以发现数组的所有元素都是数字的,并且切掉了一堆每元素开销。sum方法NumPy数组可能比PyPy快或快。)

在对数日志范围内绘制这样的数据通常很好 - 这也是我选择我所做的长度的原因:

> last_plot() + scale_x_log10() + scale_y_log10()

Same plot as above but on a log-log scale.

看看他们现在的坡度大致相同吗?这意味着所有三种技术中的asymptotic complexity是相同的,O(n),只是不同的常数因子。渐近复杂度非常重要,因为它可以让您预测更大的输入需要多长时间。在这种情况下,如果我们想知道它们对原始测试用例需要多长时间,我们可以在x轴上将这三条线延伸到一百万。使用不同的大O,我们可以看到曲线,我们需要以不同的方式推断它们。

我们还可以看到sum()曲线有一个弯曲,在线性曲线上是完全不可见的;这意味着在实现中可能会有一些特殊的短列表。并且更清楚的是reduce与2中的手写for循环具有非常相同的性能但不是3; reduce不再是3中的内置函数,但它仍然在编译代码中实现,因此我对此没有解释。 我们可以看到PyPy在开始时以一种不可预测的方式显着:那是因为基准测试功能的及时编译的成本归因于早期电话。我可以加一个&#34;热身&#34;走向基准并让它消失,但了解它是一件好事。

另一方面,CPython 3明显慢于CPython 2的事实在对数日志图中更难看到。

答案 1 :(得分:2)

我为sum获得了截然不同的时间。

您可能希望使用timeit作为计算小代码时间的更好方法。这是一个例子:

from __future__ import print_function
import operator
from functools import reduce

def f1(l):
    return sum(l)

def f2(l):
    return reduce(operator.add, l)

def f3(l):
    s=0
    for e in l:
        s+=e
    return s    

if __name__=='__main__':
    import timeit 
    import random  
    l=[random.randrange(10000) for _ in range(1000000)] 
    for f in (f1, f2, f3):
        print("   ",f.__name__, timeit.timeit("f(l)", setup="from __main__ import f, l", number=100) )     

Python3打印:

f1 0.7481771620000472
f2 6.92161642699989
f3 5.201012654999886

Python2打印:

f1 0.554444074631
f2 4.81940102577
f3 3.65543603897

PyPy:

f1 0.108825922012
f2 0.112055063248
f3 0.105736970901

答案 2 :(得分:-1)

在读完你的问题之后,我想到了第一件事:

  

使用for循环可能比使用reduce

更慢

这是基于某些文档或数据还是仅仅是假设?我读了一下,看起来这只是一个假设。

基于functional programming

上的python文档
  

功能设计可能看起来像是一个奇怪的约束。为什么要避免物体和副作用?功能风格具有理论和实践优势:

     
      
  • 形式可证明。
  •   
  • 模块化。
  •   
  • 组合性。
  •   
  • 易于调试和测试。
  •   

速度似乎不是一个优势。如果有的话,我认为由于调用函数的开销很慢(你的经验数据支持这个)。

此外,sum更快,因为它在c

中实施