Python 更有效的循环方式?
我从一个大得多的脚本中得到了一小段代码。我发现当调用函数Python 更有效的循环方式?,python,performance,for-loop,vectorization,micro-optimization,Python,Performance,For Loop,Vectorization,Micro Optimization,我从一个大得多的脚本中得到了一小段代码。我发现当调用函数t\u area时,它负责大部分运行时间。我自己测试了这个函数,它并不慢,它需要很多时间,因为我相信它需要运行很多次。以下是调用函数的代码: tri_area = np.zeros((numx,numy),dtype=float) for jj in range(0,numy-1): for ii in range(0,numx-1): xp = x[ii,jj] yp = y[ii,jj] zp
t\u area
时,它负责大部分运行时间。我自己测试了这个函数,它并不慢,它需要很多时间,因为我相信它需要运行很多次。以下是调用函数的代码:
tri_area = np.zeros((numx,numy),dtype=float)
for jj in range(0,numy-1):
for ii in range(0,numx-1):
xp = x[ii,jj]
yp = y[ii,jj]
zp = surface[ii,jj]
ap = np.array((xp,yp,zp))
xp = xp+dx
zp = surface[ii+1,jj]
bp = np.array((xp,yp,zp))
yp = yp+dx
zp = surface[ii+1,jj+1]
dp = np.array((xp,yp,zp))
xp = xp-dx
zp = surface[ii,jj+1]
cp = np.array((xp,yp,zp))
tri_area[ii,jj] = t_area(ap,bp,cp,dp)
此处使用的数组大小为216 x 217
,因此x
和y
的值也是如此。我对python编码相当陌生,过去我使用过MATLAB。所以我的问题是,有没有一种方法可以绕过这两个for循环,或者有一种更有效的方法来运行这段代码?正在寻找任何帮助加快这一进程!谢谢
编辑:
谢谢大家的帮助,这已经消除了很多困惑。我被问及循环中使用的函数t_区域,下面是代码:
def t_area(a,b,c,d):
ab=b-a
ac=c-a
tri_area_a = 0.5*linalg.norm(np.cross(ab,ac))
db=b-d
dc=c-d
tri_area_d = 0.5*linalg.norm(np.cross(db,dc))
ba=a-b
bd=d-b
tri_area_b = 0.5*linalg.norm(np.cross(ba,bd))
ca=a-c
cd=d-c
tri_area_c = 0.5*linalg.norm(np.cross(ca,cd))
av_area = (tri_area_a + tri_area_b + tri_area_c + tri_area_d)*0.5
return(av_area)
抱歉让人困惑的符号,在当时它是有意义的,现在回头看,我可能会改变它。谢谢 在我们开始之前先提出警告<代码>范围(0,numy-1),它等于
范围(numy-1)
,生成从0到numy-2的数字,不包括numy-1。这是因为numy-1的值从0到numy-2。虽然MATLAB有基于1的索引,Python有基于0的索引,所以在转换过程中要小心索引。考虑到您有tri_-area=np.zero((numx,numy),dtype=float)
,tri_-area[ii,jj]
从不以设置循环的方式访问最后一行或最后一列。因此,我怀疑正确的意图是编写range(numy)
由于函数t_area()
是可矢量化的,因此可以完全消除循环。矢量化意味着numpy通过处理引擎盖下的环路,同时对整个阵列应用一些操作,这样会更快
首先,我们为(m,n,3)数组中的每个(i,j)元素堆叠所有的ap
s,其中(m,n)是x
的大小。如果我们取两个(m,n,3)数组的叉积,默认情况下,该操作将应用于最后一个轴。这意味着np。对于每个元素(i,j),交叉(a,b)
将取a[i,j]
和b[i,j]
中3个数字的叉积。类似地,np.linalg.norm(a,轴=2)
将为每个元素(i,j)计算a[i,j]
中3个数字的范数。这也将有效地将我们的数组减小到大小(m,n)。不过这里需要注意一点,因为我们需要明确说明我们希望在第二轴上完成此操作
请注意,在下面的示例中,我的索引关系可能与您的不一致。这项工作的最低要求是surface
从x
和y
中增加一行和一列
import numpy as np
def _t_area(a, b, c):
ab = b - a
ac = c - a
return 0.5 * np.linalg.norm(np.cross(ab, ac), axis=2)
def t_area(x, y, surface, dx):
a = np.zeros((x.shape[0], y.shape[0], 3), dtype=float)
b = np.zeros_like(a)
c = np.zeros_like(a)
d = np.zeros_like(a)
a[...,0] = x
a[...,1] = y
a[...,2] = surface[:-1,:-1]
b[...,0] = x + dx
b[...,1] = y
b[...,2] = surface[1:,:-1]
c[...,0] = x
c[...,1] = y + dx
c[...,2] = surface[:-1,1:]
d[...,0] = bp[...,0]
d[...,1] = cp[...,1]
d[...,2] = surface[1:,1:]
# are you sure you didn't mean 0.25???
return 0.5 * (_t_area(a, b, c) + _t_area(d, b, c) + _t_area(b, a, d) + _t_area(c, a, d))
nx, ny = 250, 250
dx = np.random.random()
x = np.random.random((nx, ny))
y = np.random.random((nx, ny))
surface = np.random.random((nx+1, ny+1))
tri_area = t_area(x, y, surface, dx)
本例中的x
支持索引0-249,而surface
0-250surface[:-1]
,是surface[0:-1]
的缩写,将返回从0开始到最后一行的所有行,但不包括它-1
在MATLAB中提供相同的功能和结束
。因此,surface[:-1]
将返回索引0-249的行。类似地,surface[1://code>将返回索引1-250的行,这与您的surface[ii+1]
实现的相同
注意:在知道t_area()
可以完全矢量化之前,我已经写了这一节。因此,虽然就这个答案而言,这里的内容已经过时,但我将把它留作遗产,以展示如果函数不可矢量化,可以进行哪些优化
不要为每个元素调用函数(这很昂贵),而应该传递它x
、y、
、surface
和dx
,并在内部进行迭代。这意味着只有一个函数调用,开销更少
此外,您不应该为ap
、bp
、cp
和dp
每个循环创建一个数组,这同样会增加开销。在循环外分配它们一次,然后只更新它们的值
最后一个变化应该是循环的顺序。默认情况下,Numpy数组是行主数组(而MATLAB是列主数组),因此ii
作为外部循环的性能更好。你不会注意到你这样大小的数组之间的差异,但是,嘿,为什么不呢
总的来说,修改后的函数应该是这样的
def t_area(x, y, surface, dx):
# I assume numx == x.shape[0]. If not, pass it as an extra argument.
tri_area = np.zeros(x.shape, dtype=float)
ap = np.zeros((3,), dtype=float)
bp = np.zeros_like(ap)
cp = np.zeros_like(ap)
dp = np.zeros_like(ap)
for ii in range(x.shape[0]-1): # do you really want range(numx-1) or just range(numx)?
for jj in range(x.shape[1]-1):
xp = x[ii,jj]
yp = y[ii,jj]
zp = surface[ii,jj]
ap[:] = (xp, yp, zp)
# get `bp`, `cp` and `dp` in a similar manner and compute `tri_area[ii,jj]`
至少,您不必在每次迭代中分配新数组。您可以通过将ap、bp、cp、dp
存储在数组中并重写t\u区域来迭代这些数组,从而节省约40000个函数调用。另外,您也可以不使用np.array
只存储3个值。您能告诉我们在函数t\u area()
中发生了什么吗?它是矢量化的吗?好消息是你的函数可以完全矢量化。为了考虑到这一点,我编辑了我的答案。我对措辞做了一些调整,使问题的矢量化处于首位。否则,添加的内容不多。非常感谢!您让我对python结构有了更好的理解!我在完整的脚本中还有几个其他地方,在这些地方,这个实现将非常有用!与最终计算的数据相比,现在输入到这个脚本中的数据很小。我前面提到的数组大小来自~2mb的文本文件,而稍后来自文本文件的数据将为~1.5-2GB!所以这肯定会加快速度!