【问题标题】:Efficiency of numpy.dot with dynamic vs. static arraysnumpy.dot 与动态与静态数组的效率
【发布时间】:2020-09-19 07:23:55
【问题描述】:

我必须在我的数据处理管道中做很多 dot 产品。因此,我正在试验以下两段代码,其中一段的效率是最慢的代码的 3 倍(就运行时间而言)。

最慢的方法(动态创建数组)

In [33]: %timeit np.dot(np.arange(200000), np.arange(200000, 400000)) 
352 µs ± 958 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)

最快的方法(使用静态数组)

In [34]: vec1_arr = np.arange(200000) 
In [35]: vec2_arr = np.arange(200000, 400000) 

In [36]: %timeit np.dot(vec1_arr, vec2_arr) 
121 µs ± 90.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

为什么第一种动态生成数组的方法比第二种方法慢 3 倍?是因为在第一种方法中,这些额外的大部分时间都花在为元素分配内存上吗?还是其他一些导致这种退化的因素?


为了获得更多理解,我还在纯 Python 中复制了设置。令人惊讶的是,以一种或另一种方式执行它并没有性能差异,尽管它比 numpy 实现慢,这是显而易见的和预期的。

In [42]: %timeit sum(map(operator.mul, range(200000), range(200000, 400000)))
12.5 ms ± 71.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [38]: vec1 = range(200000) 
In [39]: vec2 = range(200000, 400000)

In [40]: %timeit sum(map(operator.mul, vec1, vec2)) 
12.5 ms ± 27.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

纯 Python 情况下的行为很清楚,因为 range 函数实际上并没有创建所有这些元素。它进行惰性评估(即它是动态生成的)。

注意:纯 Python 实现。只是为了让自己相信数组分配可能是造成拖累的因素。这并不意味着将其与 NumPy 实现进行比较。

【问题讨论】:

  • 关于你的第一个问题,是的,这可能是由于分配。尝试仅对分配进行计时。所有子步骤的总和应为 ~350 µs。为了进一步确认,从我不久前做的一些测试来看,即使是广播数组(不消耗额外内存)似乎也需要相同的时间来进行元素操作(例如+)。也许点产品的情况有所不同?我对此表示怀疑。
  • 第一个包括创建数组所需的时间,大arange。动态与静态在 numpy 中不是有效的区别。 dot 得到相同的数组。

标签: python performance numpy numpy-ndarray


【解决方案1】:

速度上的差异是由于在较慢的情况下分配数组。我粘贴了%timeit 的输出,它考虑了两种情况下的数组分配。 OP 的 timeit 命令只考虑了较慢情况下的分配,而不考虑较快情况下的分配。

%timeit np.dot(np.arange(200000), np.arange(200000, 400000)) 
# 524 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit vec1_arr = np.arange(200000);  vec2_arr = np.arange(200000, 400000); np.dot(vec1_arr, vec2_arr) 
# 523 µs ± 17.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

在我的机器上分配两个数组大约需要 360 微秒,np.dot 操作需要 169 微秒。这两个持续时间之和为529微秒,相当于上面%timeit输出的输出。

%timeit vec1_arr = np.arange(200000);  vec2_arr = np.arange(200000, 400000)
# 360 µs ± 16.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

vec1_arr = np.arange(200000) 
vec2_arr = np.arange(200000, 400000) 

%timeit np.dot(vec1_arr, vec2_arr) 
# 169 µs ± 5.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

【讨论】:

    【解决方案2】:

    纯 Python 测试是不公平的。因为np.arange(200000) 确实返回了一个数组,而range(200000) 只返回了一个生成器。所以这两种方法都可以动态创建数组。

    import operator
    
    %timeit sum(map(operator.mul, range(200000), range(200000, 400000)))
    # 15.1 ms ± 45.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    vec1 = range(200000)
    vec2 = range(200000, 400000)
    %timeit sum(map(operator.mul, vec1, vec2)) 
    # 15.2 ms ± 117 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    
    vec1 = list(range(200000))
    vec2 = list(range(200000, 400000))
    
    %timeit sum(map(operator.mul, vec1, vec2)) 
    # 12.4 ms ± 716 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    我们可以看到分配的时间成本:

    import numpy as np
    
    %timeit np.arange(200000), np.arange(200000, 400000)
    # 632 µs ± 9.45 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    %timeit np.dot(np.arange(200000), np.arange(200000, 400000)) 
    # 703 µs ± 5.27 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    vec1_arr = np.arange(200000) 
    vec2_arr = np.arange(200000, 400000) 
    
    %timeit np.dot(vec1_arr, vec2_arr) 
    # 77.7 µs ± 427 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    

    有道理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-07-29
      • 2019-03-25
      • 2014-07-21
      • 2011-02-09
      • 2012-01-27
      • 2013-07-20
      • 1970-01-01
      相关资源
      最近更新 更多