为什么在我的例子中 For 循环比 Map、Reduce 和 List 理解更快

Why is in my case For loop faster vs Map, Reduce and List comprehension

我写了一个简单的脚本来测试速度,这是我发现的。实际上,就我而言,for 循环是最快的。这真的让我感到惊讶,请查看下面的内容(正在计算平方和)。那是因为它在内存中保存列表还是故意的?谁能解释一下。

from functools import reduce
import datetime


def time_it(func, numbers, *args):
    start_t = datetime.datetime.now()
    for i in range(numbers):
        func(args[0])
    print (datetime.datetime.now()-start_t)

def square_sum1(numbers):
    return reduce(lambda sum, next: sum+next**2, numbers, 0)


def square_sum2(numbers):
    a = 0
    for i in numbers:
        i = i**2
        a += i
    return a

def square_sum3(numbers):
    sqrt = lambda x: x**2
    return sum(map(sqrt, numbers))

def square_sum4(numbers):
    return(sum([i**2 for i in numbers]))


time_it(square_sum1, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum2, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum3, 100000, [1, 2, 5, 3, 1, 2, 5, 3])
time_it(square_sum4, 100000, [1, 2, 5, 3, 1, 2, 5, 3])

0:00:00.302000 #Reduce
0:00:00.144000 #For loop
0:00:00.318000 #Map
0:00:00.290000 #List comprehension`

更新 - 当我尝试更长的循环时有结果。

time_it(square_sum1, 100, range(1000))
time_it(square_sum2, 100, range(1000))
time_it(square_sum3, 100, range(1000))
time_it(square_sum4, 100, range(1000))

0:00:00.068992
0:00:00.062955
0:00:00.069022
0:00:00.057446

Python function calls have overheads 这使得它们相对较慢,因此使用简单表达式的代码总是比将表达式包装在函数中的代码更快;它是普通的 def 函数还是 lambda 函数并不重要。出于这个原因,最好避免 mapreduce 如果你要传递给他们一个 Python 函数,如果你可以用 [=18= 中的普通表达式完成等效的工作] 循环或理解或生成器表达式。


有几个小优化可以加快您的某些功能。不要做不必要的任务。例如,

def square_sum2a(numbers):
    a = 0
    for i in numbers:
        a += i ** 2
    return a

此外,i * ii ** 2 快很多,因为乘法比求幂快。

正如我在评论中提到的,传递 sum 生成器比列表理解更有效,尤其是在循环很大的情况下;对于长度为 8 的小列表,它可能不会有什么不同,但对于大列表,它会非常明显。

sum(i*i for i in numbers)

顺便说一句,您不应该使用 sumnext 作为变量名,因为这会掩盖具有相同名称的内置函数。它在这里不会造成任何伤害,但这仍然不是一个好主意,并且它使您的代码在语法突出显示比 SO 语法突出显示更全面的编辑器中看起来很奇怪。


这是使用 timeit 模块的新代码版本。它执行 3 次重复,每次重复 10,000 次循环并对结果进行排序。正如 timeit docs 中所解释的,重复系列中要查看的重要数字是最小的数字。

In a typical case, the lowest value gives a lower bound for how fast your machine can run the given code snippet; higher values in the result vector are typically not caused by variability in Python’s speed, but by other processes interfering with your timing accuracy. So the min() of the result is probably the only number you should be interested in.

from timeit import Timer
from functools import reduce

def square_sum1(numbers):
    return reduce(lambda total, u: total + u**2, numbers, 0)

def square_sum1a(numbers):
    return reduce(lambda total, u: total + u*u, numbers, 0)

def square_sum2(numbers):
    a = 0
    for i in numbers:
        i = i**2
        a += i
    return a

def square_sum2a(numbers):
    a = 0
    for i in numbers:
        a += i * i
    return a

def square_sum3(numbers):
    sqr = lambda x: x**2
    return sum(map(sqr, numbers))

def square_sum3a(numbers):
    sqr = lambda x: x*x
    return sum(map(sqr, numbers))

def square_sum4(numbers):
    return(sum([i**2 for i in numbers]))

def square_sum4a(numbers):
    return(sum(i*i for i in numbers))

funcs = (
    square_sum1,
    square_sum1a,
    square_sum2,
    square_sum2a,
    square_sum3,
    square_sum3a,
    square_sum4,
    square_sum4a,
)

data = [1, 2, 5, 3, 1, 2, 5, 3]

def time_test(loops, reps):
    ''' Print timing stats for all the functions '''
    timings = []
    for func in funcs:
        fname = func.__name__
        setup = 'from __main__ import data, ' + fname
        cmd = fname + '(data)'
        t = Timer(cmd, setup)
        result = t.repeat(reps, loops)
        result.sort()
        timings.append((result, fname))

    timings.sort()
    for result, fname in timings:
        print('{0:14} {1}'.format(fname, result))

loops, reps = 10000, 3
time_test(loops, reps)

输出

square_sum2a   [0.03815755599862314, 0.03817843700016965, 0.038571521999983815]
square_sum4a   [0.06384095800240175, 0.06462285799716483, 0.06579178199899616]
square_sum3a   [0.07395686000018031, 0.07405958899835241, 0.07463337299850537]
square_sum1a   [0.07867341000019223, 0.0788448769999377, 0.07908406700153137]
square_sum2    [0.08781023399933474, 0.08803317899946705, 0.08846573399932822]
square_sum4    [0.10260082300010254, 0.10360279499946046, 0.10415067900248687]
square_sum3    [0.12363515399920288, 0.12434166299863136, 0.1273790529994585]
square_sum1    [0.1276186039976892, 0.13786184099808452, 0.16315817699796753]

结果是在一台旧的单核 32 位 2GHz 机器上获得的 运行 Python 3.6.0 on Linux.

这几乎独立于底层编程语言,因为 抽象 不是免费的。

意思是:调用方法总是有一定的代价的。需要建立栈;控制流需要 "jump"。当您想到较低级别时,例如 CPU:可能需要加载该方法的 code,等等。

换句话说:当您的主要需求是硬核数字运算时,您必须在易用性和相应抽象的成本之间取得平衡。

超越:如果您关注速度,那么您应该超越 python,或者至少超越 "ordinary" python。相反,您可以转向 numpy.

等模块