【问题标题】:Python FOR loop vs. builtin reduce() and mul() functions?Python FOR 循环与内置 reduce() 和 mul() 函数?
【发布时间】:2019-09-21 21:36:34
【问题描述】:

这里我有两个功能:

from functools import reduce
from operator import mul

def fn1(arr):
    product = 1
    for number in arr:
        product *= number
    return [product//x for x in arr]

def fn2(arr):
   product = reduce(mul, arr)
   return [product//x for x in arr]

基准测试:

In [2]: arr = list(range(1,11))

In [3]: %timeit fn1(arr)
1.62 µs ± 23.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [4]: %timeit fn2(arr)
1.88 µs ± 28.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [5]: arr = list(range(1,101))

In [6]: %timeit fn1(arr)
38.5 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [7]: %timeit fn2(arr)
41 µs ± 463 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [8]: arr = list(range(1,1001))

In [9]: %timeit fn1(arr)
4.23 ms ± 25.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [10]: %timeit fn2(arr)
4.24 ms ± 36.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [11]: arr = list(range(1,10001))

In [12]: %timeit fn1(arr)
605 ms ± 4.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [13]: %timeit fn2(arr)
594 ms ± 4.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

这里fn2() 的小列表稍微慢一些。我的理解是reduce()mul() 函数都是内置函数,因此它们以C 的速度运行并且应该比for 循环更快。可能是因为我在fn2 中有更多的函数调用(这也需要一些时间),它有助于最终性能?但随后趋势表明,fn2() 的性能优于fn1() 的列表更大。为什么?

【问题讨论】:

  • 这些都是非常接近的时间度量,我认为一种或另一种方法之间确实没有显着差异。 reduce 以防万一您更喜欢功能性更强的编程方式,它不应该是一种优化。此外,您在函数中有一个列表理解,无论如何可能要花费大部分时间,特别是考虑到您正在用数以千计的数字除数(10,000!需要超过 100,000 位来存储)。
  • 推测这些微小的差异可能是由额外的调用引起的——for循环和算术操作可能比调用mul的开销要快一点。 (似乎标准的 CPython 解释器不做内联。stackoverflow.com/questions/6442050/…,请注意 PyPy 如何因其内联支持而得到特别提及)。你不应该太担心这个;如果这种规模的性能很重要,那么 Python 可能是一个错误的工具。 (虽然你可以试试 PyPy。)
  • 是的,但是为什么趋势是这样的呢?如果for 循环 + 算术运算 (fn1) 的计算时间比 reduce() + mul() (mul() (fn2) 少,那么无论列表大小如何,它都应该保持正确,对吧?此外,列表中的项目越多,fn1fn2 之间的计算距离就会增加,因为函数调用越多,它就需要更多的时间。但我观察到相反的情况,似乎列表中的项目越多fn2 神奇地开始表现得比fn1 更好。

标签: python python-3.x


【解决方案1】:

这可能有很多原因。首先是 CPU 代码执行预测和编译器优化。至于我,如果您选择适当的算法,哪种形式的代码更快对您来说并不重要。您需要使用一种适合您的需求并且看起来更好的,并将性能留给 python 编译器。通常更快的选项会导致更多的内存/可读性/支持问题。此外,也不能保证简单循环中稍微复杂一点的代码不会改变性能,只是因为可以应用一些优化。

-- 更新--

如果您想提高 python 在简单操作上的性能,我建议您查看 PyPy、Cython、nim,它们是为了在 python 类型包装器占用过多时获得 C 的性能。

【讨论】:

    【解决方案2】:

    这些总是非常接近,但出于一些有趣的原因:

      1234563调用
    1. 如果乘积很大(例如 range(1,10001) 的情况),数字会变得很大(即数千个十进制数字),并且大部分时间都花在乘以非常大的数字上

    例如,使用 Python 3.7.3(在 Linux 5.0.10 中):

    from functools import reduce
    from operator import mul
    
    prod = reduce(mul, range(1, 10001))
    

    prod 有约 36k 位并消耗约 16KiB --- 即检查 math.log10(prod)sys.getsizeof(prod)

    使用小型(即不是 bignum)产品,例如:

    reduce(mul, [1] * 10001)
    

    在我的计算机上比我们需要使用上面的 bignums 时快约 50 倍。另请注意,与整数相比,使用浮点数的速度基本相同,例如

    reduce(mul, [1.] * 10001)
    

    只需要多出约 10% 的时间。

    您对数组进行额外传递的附加代码似乎只是使问题复杂化,因此我忽略了它 --- 像这样的微基准测试很尴尬,无法正确处理!

    【讨论】:

      猜你喜欢
      • 2016-02-18
      • 1970-01-01
      • 2017-09-19
      • 2019-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多