Python numpy阵列变换中的性能改进

Python numpy阵列变换中的性能改进,python,performance,numpy,matplotlib,Python,Performance,Numpy,Matplotlib,给定三个numpy1D数组,我想对它们进行如下转换: import numpy as np Xd = np.asarray([0, 0, 1, 1, 0.5]) Yd = np.asarray([0, 0, 0, 2.5, 2.5]) Zd = np.asarray([0, 1.5, 1.5, 1.5, 1.5]) points = np.stack([Xd, Yd, Zd], axis=1).reshape(-1, 1, 3) segments = np.concate

给定三个
numpy
1D数组,我想对它们进行如下转换:

import numpy as np

Xd = np.asarray([0, 0,   1,   1,   0.5])
Yd = np.asarray([0, 0,   0,   2.5, 2.5])
Zd = np.asarray([0, 1.5, 1.5, 1.5, 1.5])

points = np.stack([Xd, Yd, Zd], axis=1).reshape(-1, 1, 3)
segments = np.concatenate([points[:-1], points[1:]], axis = 1)    

print(segments.shape)
print(segments)
输出:

(4, 2, 3)
[[[0.  0.  0. ]
  [0.  0.  1.5]]

 [[0.  0.  1.5]
  [1.  0.  1.5]]

 [[1.  0.  1.5]
  [1.  2.5 1.5]]

 [[1.  2.5 1.5]
  [0.5 2.5 1.5]]]
有没有办法提高此转换的性能

背景

使用
matplotlib
中的
XYZ
坐标时,必须进行此转换。到目前为止,我只看到了上述代码的变体,但为了获得更好的分辨率,需要使用或插值数据,因此需要一种优化方法

摘要


多亏了,可以得出结论,对于较短的数组(作为一般建议,当您需要速度时,通常应该避免堆栈和连接,因为这通常意味着要多次复制相同的数据

无论如何,我会这样做,代码稍微长一点,但做的工作不会超过需要

n = len(Xd)
segments = np.empty((n-1, 2, 3))

segments[:,0,0] = Xd[:-1]
segments[:,1,0] = Xd[1:]

segments[:,0,1] = Yd[:-1]
segments[:,1,1] = Yd[1:]

segments[:,0,2] = Zd[:-1]
segments[:,1,2] = Zd[1:]
[编辑]-以下内容是为了科学/娱乐而制作的,请勿复制

所以我试着看看是否可以从的答案中挤出更多的性能,然后我得出了这个丑陋的代码:

a = np.empty(3*n)
a[:n]    = Xd
a[n:n+n] = Yd
a[n+n:]  = Zd

interface = dict(a.__array_interface__)
interface['shape'] = (n-1, 2, 3)
interface['strides'] = (a.itemsize, a.itemsize, n*a.itemsize)
segments= np.array(np.lib.stride_tricks.DummyArray(interface, base=a), copy=False)

在我的机器上,速度要快得多(根据输入的大小高达30%)。收益的部分原因是由于构建了
a
并跳过了
的检查。作为一般建议,当需要速度时,通常应避免堆叠和连接,因为这通常意味着要多次复制相同的数据

无论如何,我会这样做,代码稍微长一点,但做的工作不会超过需要

n = len(Xd)
segments = np.empty((n-1, 2, 3))

segments[:,0,0] = Xd[:-1]
segments[:,1,0] = Xd[1:]

segments[:,0,1] = Yd[:-1]
segments[:,1,1] = Yd[1:]

segments[:,0,2] = Zd[:-1]
segments[:,1,2] = Zd[1:]
[编辑]-以下内容是为了科学/娱乐而制作的,请勿复制

所以我试着看看是否可以从的答案中挤出更多的性能,然后我得出了这个丑陋的代码:

a = np.empty(3*n)
a[:n]    = Xd
a[n:n+n] = Yd
a[n+n:]  = Zd

interface = dict(a.__array_interface__)
interface['shape'] = (n-1, 2, 3)
interface['strides'] = (a.itemsize, a.itemsize, n*a.itemsize)
segments= np.array(np.lib.stride_tricks.DummyArray(interface, base=a), copy=False)

在我的机器上,它的速度要快得多(根据输入的大小高达30%),部分原因是由于
a
的构造和跳过
的检查,我发现这比@Miguel的代码快

z = [i for i in zip(Xd,Yd,Zd)]
segments = [[[z[i]],[z[i+1]]] for i in range(len(z)-1)]

我发现这比@Miguel的代码快

z = [i for i in zip(Xd,Yd,Zd)]
segments = [[[z[i]],[z[i+1]]] for i in range(len(z)-1)]

以下是一些在较大阵列上进行的计时测试,这使得差异更加明显

import numpy as np
from timeit import timeit

# original
def f1(x, y, z):
    points = np.stack([x, y, z], axis=1).reshape(-1, 1, 3)
    return np.concatenate([points[:-1], points[1:]], axis = 1)

# preallocating and then assigning
def f2(x, y, z):
    segments = np.empty((len(x)-1, 2, 3))

    segments[:,0,0] = x[:-1]
    segments[:,1,0] = x[1:]

    segments[:,0,1] = y[:-1]
    segments[:,1,1] = y[1:]

    segments[:,0,2] = z[:-1]
    segments[:,1,2] = z[1:]
    return segments

# stacking, but in one go
def f3(x, y, z):
    segments = np.stack([x[:-1], y[:-1], z[:-1], x[1:], y[1:],z[1:]], axis=1)
    return segments.reshape(-1, 2, 3)

# list comparison
def f4(x, y, z):
    z_ = [i for i in zip(x,y,z)]
    return [[[z_[i]],[z_[i+1]]] for i in range(len(z_)-1)]

#np.lib.stride_tricks approach
def f5(x, y, z):
    a = np.transpose([x, y, z])
    window = (2, 3)
    view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
    return np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
    

ntime = 5000 #number of test runs
nxd = 500    #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#0.11369249999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#0.0480651

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#0.10202380000000003

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#1.8407391

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.09132560000000023
    
ntime = 50     #number of test runs
nxd = 500000   #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#1.7519548999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#1.504727

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#1.5010566

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#22.6208157

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.46465339999999955

正如您所看到的,@Miguel的方法就是这样:预分配数组,然后分配是最有效的方法。即使您以更智能的方式(如f3()中)堆叠它们,它仍然比f2()慢。但没有什么比f5()更有效当阵列长度大幅增加时。

以下是一些在较大阵列上进行的计时测试,这使得差异更加明显

import numpy as np
from timeit import timeit

# original
def f1(x, y, z):
    points = np.stack([x, y, z], axis=1).reshape(-1, 1, 3)
    return np.concatenate([points[:-1], points[1:]], axis = 1)

# preallocating and then assigning
def f2(x, y, z):
    segments = np.empty((len(x)-1, 2, 3))

    segments[:,0,0] = x[:-1]
    segments[:,1,0] = x[1:]

    segments[:,0,1] = y[:-1]
    segments[:,1,1] = y[1:]

    segments[:,0,2] = z[:-1]
    segments[:,1,2] = z[1:]
    return segments

# stacking, but in one go
def f3(x, y, z):
    segments = np.stack([x[:-1], y[:-1], z[:-1], x[1:], y[1:],z[1:]], axis=1)
    return segments.reshape(-1, 2, 3)

# list comparison
def f4(x, y, z):
    z_ = [i for i in zip(x,y,z)]
    return [[[z_[i]],[z_[i+1]]] for i in range(len(z_)-1)]

#np.lib.stride_tricks approach
def f5(x, y, z):
    a = np.transpose([x, y, z])
    window = (2, 3)
    view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
    return np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
    

ntime = 5000 #number of test runs
nxd = 500    #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#0.11369249999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#0.0480651

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#0.10202380000000003

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#1.8407391

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.09132560000000023
    
ntime = 50     #number of test runs
nxd = 500000   #array length

Xd = np.random.randn(nxd)
Yd = np.random.randn(nxd)
Zd = np.random.randn(nxd)

print(timeit(lambda: f1(Xd, Yd, Zd), number=ntime))
#1.7519548999999999

print(timeit(lambda: f2(Xd, Yd, Zd), number=ntime))
#1.504727

print(timeit(lambda: f3(Xd, Yd, Zd), number=ntime))
#1.5010566

print(timeit(lambda: f4(Xd, Yd, Zd), number=ntime))
#22.6208157

print(timeit(lambda: f5(Xd, Yd, Zd), number=ntime))
#0.46465339999999955

正如您所看到的,@Miguel的方法就是这样:预分配数组,然后分配是最有效的方法。即使您以更智能的方式(如f3()中)堆叠它们,它仍然比f2()慢。但没有什么比f5()更有效当数组长度大幅增加时。

看起来您试图在2D数组中滚动一个形状为
(2,3)
的窗口。这与使用
np.lib.stride\u技巧以非常有效的方式进行类似

a = np.transpose([Xd, Yd, Zd])
window = (2, 3)
view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
sub_matrix = np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
>>> sub_matrix
array([[[0. , 0. , 0. ],
        [0. , 0. , 1.5]],

       [[0. , 0. , 1.5],
        [1. , 0. , 1.5]],

       [[1. , 0. , 1.5],
        [1. , 2.5, 1.5]],

       [[1. , 2.5, 1.5],
        [0.5, 2.5, 1.5]]])

请注意,
np.lib.stride\u技巧
对任何其他方法都非常有效。

似乎您试图在2D数组中滚动形状为
(2,3)
的窗口。这与使用
np.lib.stride\u技巧
可以非常有效地完成的操作类似

a = np.transpose([Xd, Yd, Zd])
window = (2, 3)
view_shape = (len(a) - window[0] + 1,) + window # (4,2,3) if len(a) == 5
sub_matrix = np.lib.stride_tricks.as_strided(a, shape = view_shape, strides = (a.itemsize,) + a.strides)
>>> sub_matrix
array([[[0. , 0. , 0. ],
        [0. , 0. , 1.5]],

       [[0. , 0. , 1.5],
        [1. , 0. , 1.5]],

       [[1. , 0. , 1.5],
        [1. , 2.5, 1.5]],

       [[1. , 2.5, 1.5],
        [0.5, 2.5, 1.5]]])


请注意,
np.lib.stride\u技巧
对于任何替代方法都非常有效。

不太可能。乍一看,我可以看出这应该扩展得非常糟糕,实际上比OP的原始代码更糟糕。请尝试使用Xd、Yd、Zd作为更长的1d数组进行%timeit测试,可能长度为500。@Mercury啊..我用错误的方式进行测试是因为我认为x,y,z的长度将保持不变的原因。我的不好。添加了我的测试作为参考。可能性很小。乍一看,我可以看出这应该扩展得很差,实际上比OP的原始代码更差。尝试使用Xd,Yd,Zd作为长1d数组的%timeit测试,可能长度为500。@Mercury啊..为此我做了错误的测试我认为x,y,z的长度将保持不变的原因。我的错。添加了我的测试作为参考。哦,这相当大,所以我想这个方法没有那么有趣。如果你想看一看,我在mathfux的答案上添加了一个变体,尽管我不建议使用它。也就是说,我认为他的答案中转置的构造我更喜欢你早期的作品。至少对我来说,这是不可读的。我真的很抱歉,我将不得不接受另一个答案而不是你的答案。我喜欢你最初解决方案的可读性,它对较小阵列的性能非常好,但问题是对大型阵列的性能-缩放数组。别担心,我完全同意你的看法。哦,这太大了,所以我想这种方法没有那么有趣。如果你想看一下的话,我在mathfux的答案上添加了一个变体,尽管我不推荐使用它。尽管如此,我认为他的答案中转置的构造可以用一点bi来代替t fasterI我更喜欢你早期的作品。至少对我来说,这是不可读的。我真的很抱歉,我将不得不接受另一个答案,而不是你的答案。我喜欢你最初解决方案的可读性,它对较小阵列的性能非常好,但问题是关于大规模阵列的性能。别担心,我完全同意w这真是令人印象深刻!的确。与原始方法和Miguel的方法相比,这种方法变得更好,数组
Xd,Yd,Zd
越长。好的,T先生。所以我回来时对任意长度的数组进行了一个小的扩展。长度为1000的样本需要25µs,长度为1M.H的样本需要15 ms正如有人提到的那样,这种性能改进令人印象深刻吗?@T先生,我想每个人都应该从中学习很多。他在个人资料中提到步幅技巧是一种超级工具。T