【问题标题】:Performance between C-contiguous and Fortran-contiguous array operationsC 连续和 Fortran 连续数组操作之间的性能
【发布时间】:2016-12-27 12:42:37
【问题描述】:

下面,我比较了处理 C 连续数组和 Fortran 连续数组 (C vs FORTRAN memory order) 之间的求和运算时的性能。我设置axis=0 以确保数字按列相加。我很惊讶 Fortran 连续数组实际上比它的 C 对应物慢。是不是 Fortran 连续数组在列中具有连续的内存分配,因此在按列操作时更好?

import numpy as np
a = np.random.standard_normal((10000, 10000))
c = np.array(a, order='C')
f = np.array(a, order='F')

在 Jupyter 笔记本中,运行

%timeit c.sum(axis=0)
10 loops, best of 3: 84.6 ms per loop
%timeit f.sum(axis=0)
10 loops, best of 3: 137 ms per loop

【问题讨论】:

  • 您正在这样做,但使用的是 Python...不是有一些仿真吗?
  • 无法验证这一点。对我来说,b.sum(axis=0) 是 2.51 秒,c.sum(axis=0) 是 0.14 秒
  • 您使用的是哪个版本的numpy
  • np.add.reduce(x, axis=0) 表现出相同的行为,这也是 sum 在幕后所做的
  • 开始实施here

标签: python numpy multidimensional-array scipy


【解决方案1】:

这是意料之中的。如果您检查

的结果
%timeit f.sum(axis=1)

它也给出了与c 的时间相似的结果。同样,

%timeit c.sum(axis=1)

较慢。


一些解释:假设你有以下结构

|1| |6|
|2| |7|
|3| |8|
|4| |9|
|5| |10|

正如 Eric 所提到的,这些操作适用于 reduce。假设我们要求列总和。如此直观的机制并没有发挥作用,以至于每一列都被访问一次、求和并记录。事实上恰恰相反,每一行都被访问并且函数(这里求和)的执行本质上类似于拥有两个数组a,b并执行

a += b

这是重复documentation of reduce 中超级神秘提到的内容的一种非常非正式的方式。 这需要连续访问行,尽管我们正在执行列和 [1,6] + [2,7] + [3,8]... 因此,实现方向取决于操作而不是数组。

【讨论】:

    【解决方案2】:

    我认为这是在 np.sum() 的实现中。例如:

    import numpy as np
    
    A = np.random.standard_normal((10000,10000))
    C = np.array(A, order='C')
    F = np.array(A, order='F')
    

    使用 Ipython 进行基准测试:

    In [7]: %timeit C.sum(axis=0)
    10 loops, best of 3: 101 ms per loop
    
    In [8]: %timeit C.sum(axis=1)
    10 loops, best of 3: 149 ms per loop
    
    In [9]: %timeit F.sum(axis=0)
    10 loops, best of 3: 149 ms per loop
    
    In [10]: %timeit F.sum(axis=1)
    10 loops, best of 3: 102 ms per loop
    

    所以它的行为与预期完全相反。但是让我们尝试一下其他功能:

    In [17]: %timeit np.amax(C, axis=0)
    1 loop, best of 3: 173 ms per loop
    
    In [18]: %timeit np.amax(C, axis=1)
    10 loops, best of 3: 70.4 ms per loop
    
    In [13]: %timeit np.amax(F,axis=0)
    10 loops, best of 3: 72 ms per loop
    
    In [14]: %timeit np.amax(F,axis=1)
    10 loops, best of 3: 168 ms per loop
    

    当然,这是苹果对橙子。但是 np.amax() 和 sum 一样沿轴工作,并返回一个向量,每行/列都有一个元素。并且表现得如预期的那样。

    In [25]: C.strides
    Out[25]: (80000, 8)
    
    In [26]: F.strides
    Out[26]: (8, 80000)
    

    告诉我们,数组实际上是按行顺序和列顺序排列的,在那个方向上循环应该快得多。除非总和在沿列行进时逐行求和以提供列总和(轴 = 0)。但是没有办法窥视 .pyd 内部,我只是在猜测。

    编辑:

    来自 percusse 的链接:http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.reduce.html

    通过沿一个轴应用 ufunc 将 a 的维度减少一。

    令 a.shape = (N_0, ..., N_i, ..., N_{M-1})。 然后 ufunc.reduce(a, axis=i)[k_0, ..,k_{i-1}, k_{i+1}, .., k_{M-1}] = j over range( N_i),累积应用ufunc到每个 a[k_0, ..,k_{i-1}, j, k_{i+1}, .., k_{M-1}]

    所以在伪代码中,调用 F.sum(axis=0) 时:

    for j=cols #axis=0
        for i=rows #axis=1
            sum(j,i)=F(j,i)+sum(j-1,i)
    

    因此,在计算列总和时,它实际上会遍历行,而在以列为主时会显着减慢。诸如此类的行为可以解释差异。

    eric 的链接为我们提供了实现,对于那些好奇地浏览大量代码的人来说。

    【讨论】:

    • 那么您是说数组的顺序类型不一定与性能相关,我们最好对每种情况进行测试?
    • 不,肯定和性能有关。但是,是的,如果你有一些对性能至关重要的东西,那么对自己进行基准测试是一个比经验法则更好的主意,尤其是对于黑盒算法。 link 以我们预期的行为为例,指出 .sum(axis=0) 在大型矩阵上会慢几个数量级。丹尼尔在 cmets 中说他也看到了这种行为。所以它也可能取决于版本。我正在使用 Python 3.5.2 运行 Numpy 1.11.1。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 2016-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-15
    • 2015-01-15
    相关资源
    最近更新 更多