Python 在numpy数组上映射函数的最有效方法

Python 在numpy数组上映射函数的最有效方法,python,performance,numpy,Python,Performance,Numpy,在numpy数组上映射函数最有效的方法是什么?我在当前项目中的做法如下: import numpy as np x = np.array([1, 2, 3, 4, 5]) # Obtain array of square of each element in x squarer = lambda t: t ** 2 squares = np.array([squarer(xi) for xi in x]) 但是,这似乎效率很低,因为我正在使用列表理解将新数组构造为Python列表,然后再

在numpy数组上映射函数最有效的方法是什么?我在当前项目中的做法如下:

import numpy as np 

x = np.array([1, 2, 3, 4, 5])

# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])
但是,这似乎效率很低,因为我正在使用列表理解将新数组构造为Python列表,然后再将其转换回numpy数组

我们能做得更好吗?

如中所述,只需使用生成器表达式,如下所示:

numpy.fromiter((<some_func>(x) for x in <something>),<dtype>,<size of something>)
numpy.fromiter(((x)表示x英寸),)
如何使用

数组上的算术运算是按元素自动应用的,高效的C级循环避免了应用于Python级循环或理解的所有解释器开销

您希望应用于NumPy数组elementwise的大多数函数都可以正常工作,尽管有些函数可能需要更改。例如,
如果
在元素方面不起作用。您可能希望将其转换为使用以下结构:

def使用_if(x):
如果x<5:
返回x
其他:
返回x**2
变成

def using_where(x):
    return numpy.where(x < 5, x, x**2)
def使用_,其中(x):
返回数值,其中(x<5,x,x**2)
TL;博士 如所述,应用函数的“直接”方法始终是将函数映射到Numpy数组上的最快、最简单的方法:

import numpy as np
x = np.array([1, 2, 3, 4, 5])
f = lambda x: x ** 2
squares = f(x)
通常避免使用
np.vectorize
,因为它的性能不好,并且有(或曾经有)许多问题。如果您正在处理其他数据类型,则可能需要研究下面所示的其他方法

方法比较 下面是一些比较映射函数的三种方法的简单测试,本例使用Python3.6和Numpy1.15.4。首先,测试的设置功能:

import timeit
import numpy as np

f = lambda x: x ** 2
vf = np.vectorize(f)

def test_array(x, n):
    t = timeit.timeit(
        'np.array([f(xi) for xi in x])',
        'from __main__ import np, x, f', number=n)
    print('array: {0:.3f}'.format(t))

def test_fromiter(x, n):
    t = timeit.timeit(
        'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))',
        'from __main__ import np, x, f', number=n)
    print('fromiter: {0:.3f}'.format(t))

def test_direct(x, n):
    t = timeit.timeit(
        'f(x)',
        'from __main__ import x, f', number=n)
    print('direct: {0:.3f}'.format(t))

def test_vectorized(x, n):
    t = timeit.timeit(
        'vf(x)',
        'from __main__ import x, vf', number=n)
    print('vectorized: {0:.3f}'.format(t))
使用五个元素进行测试(从最快到最慢排序):

有100个元素:

x = np.arange(100)
n = 10000
test_direct(x, n)      # 0.030
test_array(x, n)       # 0.501
test_vectorized(x, n)  # 0.670
test_fromiter(x, n)    # 0.883
以及1000个或更多阵列元素:

x = np.arange(1000)
n = 1000
test_direct(x, n)      # 0.007
test_fromiter(x, n)    # 0.479
test_array(x, n)       # 0.516
test_vectorized(x, n)  # 0.945
不同版本的Python/NumPy和编译器优化会有不同的结果,因此请针对您的环境进行类似的测试。

我相信NumPy的更新版本(我使用1.13)可以通过将NumPy数组传递给为标量类型编写的函数来调用函数,它将自动将函数调用应用于numpy数组中的每个元素,并返回另一个numpy数组

>>> import numpy as np
>>> squarer = lambda t: t ** 2
>>> x = np.array([1, 2, 3, 4, 5])
>>> squarer(x)
array([ 1,  4,  9, 16, 25])

我已经用(我的一个小项目)测试了所有建议的方法以及
np.array(map(f,x))

信息#1:如果您可以使用numpy的本机函数,请这样做

如果您尝试矢量化的函数已经矢量化(如原始帖子中的
x**2
示例),则使用该函数比使用其他函数快得多(注意日志比例):

如果您真的需要矢量化,那么使用哪种变体其实并不重要


复制绘图的代码:

将numpy导入为np
导入性能图
输入数学
def f(x):
#返回math.sqrt(x)
返回np.sqrt(x)
vf=np.矢量化(f)
用于(x)的def阵列_:
返回NP.数组([X]席席的F(Xi))
def阵列_映射(x):
返回np.array(list(map(f,x)))
来自ITER的def(x):
返回NP.FrutMER((席席X(f)(x)),x.dType)
def矢量化(x):
返回np.矢量化(f)(x)
def矢量化_,不带_init(x):
返回vf(x)
perfplot.show(
设置=np.random.rand,
n_范围=[2**k表示范围(20)中的k],
内核=[f,数组,数组映射,fromiter,
向量化,向量化\u而不使用\u init],
xlabel=“len(x)”,
)
这个答案的目的是将这些可能性考虑在内

但首先让我们陈述一个显而易见的事实:无论您如何将Python函数映射到numpy数组,它始终是一个Python函数,这意味着对于每个计算:

  • numpy数组元素必须转换为Python对象(例如
    Float
  • 所有的计算都是用Python对象完成的,这意味着要有解释器、动态分派和不可变对象的开销
因此,由于上面提到的开销,用于实际循环阵列的机器并没有起到很大的作用——它比使用numpy的内置功能慢得多

让我们看一下以下示例:

# numpy-functionality
def f(x):
    return x+2*x*x+4*x*x*x

# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
np.vectorize
被选为纯python函数类方法的代表。使用
perfplot
(参见本答案附录中的代码),我们得到以下运行时间:

我们可以看到,numpy方法比纯python版本快10-100倍。较大阵列大小的性能下降可能是因为数据不再适合缓存

还值得一提的是,
vectorize
也使用了大量内存,因此内存使用往往是瓶颈(参见相关内容)。还要注意的是,numpy的文档中指出它“主要是为了方便,而不是为了性能”

当需要性能时,应使用其他工具,除了从头开始编写C扩展外,还有以下几种可能性:


人们经常听说,numpy的性能是最好的,因为它是纯C引擎盖下。然而,还有很多改进的空间

矢量化的numpy版本使用了大量额外的内存和内存访问。Numexp库尝试平铺numpy阵列,从而获得更好的缓存利用率:

# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
    return ne.evaluate("x+2*x*x+4*x*x*x")
导致以下比较:

我无法解释上图中的所有内容:一开始我们可以看到numexpr库的开销更大,但因为它更好地利用了缓存,所以对于更大的阵列,它的速度大约快了10倍


另一种方法是jit编译函数,从而获得真正的纯C UFunc。这是numba的方法:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x
它比最初的numpy方法快10倍:

# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x


然而,该任务的并行性令人尴尬,因此我们也可以使用
prange
来并行计算循环:

@nb.njit(parallel=True)
def nb_par_jitf(x):
    y=np.empty(x.shape)
    for i in nb.prange(len(x)):
        y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y
正如预期的那样,paral
# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
    return ne.evaluate("x+2*x*x+4*x*x*x")
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
    return x+2*x*x+4*x*x*x
@nb.njit(parallel=True)
def nb_par_jitf(x):
    y=np.empty(x.shape)
    for i in nb.prange(len(x)):
        y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y
%%cython -c=/openmp -a
import numpy as np
import cython

#single core:
@cython.boundscheck(False) 
@cython.wraparound(False) 
def cy_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef Py_ssize_t i
    cdef double[::1] y=y_out
    for i in range(len(x)):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out

#parallel:
from cython.parallel import prange
@cython.boundscheck(False) 
@cython.wraparound(False)  
def cy_par_f(double[::1] x):
    y_out=np.empty(len(x))
    cdef double[::1] y=y_out
    cdef Py_ssize_t i
    cdef Py_ssize_t n = len(x)
    for i in prange(n, nogil=True):
        y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
    return y_out
import perfplot
perfplot.show(
    setup=lambda n: np.random.rand(n),
    n_range=[2**k for k in range(0,24)],
    kernels=[
        f, 
        vf,
        ne_f, 
        nb_vf, nb_par_jitf,
        cy_f, cy_par_f,
        ],
    logx=True,
    logy=True,
    xlabel='len(x)'
    )
def along_axis(x):
    return np.apply_along_axis(f, 0, x)
>>> timeit()
1.162431240081787    # list comprehension and then building numpy array
1.0775556564331055   # from numpy.fromiter
0.002948284149169922 # using inbuilt function