有什么方法可以加速这个Python代码?

时间:2013-07-03 14:08:20

标签: python performance numpy

我已经编写了一些Python代码来进行一些图像处理工作,但是运行需要花费大量的时间。我花了最后几个小时试图优化它,但我想我已经达到了我的能力。

查看分析器的输出,下面的函数占我代码总时间的很大一部分。有什么办法可以加速吗?

def make_ellipse(x, x0, y, y0, theta, a, b):
    c = np.cos(theta)
    s = np.sin(theta)
    a2 = a**2
    b2 = b**2
    xnew = x - x0
    ynew = y - y0
    ellipse = (xnew * c + ynew * s)**2/a2 + (xnew * s - ynew * c)**2/b2 <= 1

    return ellipse

为了给出上下文,调用它是xy作为np.meshgrid的输出,具有相当大的网格大小,所有其他参数作为简单的整数值。

尽管该功能似乎占用了大量时间,但可能还有其他代码可以加速的方法。我已将其余代码放在this gist

任何想法都会感激不尽。我尝试过使用numba和autojit主要功能,但这没什么用。

4 个答案:

答案 0 :(得分:3)

让我们尝试将make_ellipse与其调用者一起优化。

首先,请注意ab与多次调用相同。由于make_ellipse每次都会对它们进行平方,所以只需要让调用者这样做。

其次,请注意np.cos(np.arctan(theta))1 / np.sqrt(1 + theta**2),这似乎在我的系统上稍快一些。可以使用类似的技巧来计算正弦,从theta或cos(theta)(或反之亦然)。

第三,不太具体,考虑将一些最终的椭圆公式评估短路。例如,只要(xnew * c + ynew * s)**2/a2大于1,椭圆值必须为False。如果经常发生这种情况,您可以“屏蔽”这些位置的椭圆(昂贵)计算的后半部分。我没有详细计划过,但请看numpy.ma以获取一些可能的线索。

答案 1 :(得分:2)

对于所有情况都不会加快速度,但如果您的省略号不占用整个图像,则应将椭圆内的点搜索限制为其边界矩形。我对数学很懒,所以我googled it并重新使用了@JohnZwinck反复无常的技巧来提出这个函数:

def ellipse_bounding_box(x0, y0, theta, a, b):
    x_tan_t = -b * np.tan(theta) /  a
    if np.isinf(x_tan_t) :
        x_cos_t = 0
        x_sin_t = np.sign(x_tan_t)
    else :
        x_cos_t = 1 / np.sqrt(1 + x_tan_t*x_tan_t)
        x_sin_t = x_tan_t * x_cos_t
    x = x0 + a*x_cos_t*np.cos(theta) - b*x_sin_t*np.sin(theta)

    y_tan_t = b / np.tan(theta) /  a
    if np.isinf(y_tan_t):
        y_cos_t = 0
        y_sin_t = np.sign(y_tan_t)
    else:
        y_cos_t = 1 / np.sqrt(1 + y_tan_t*y_tan_t)
        y_sin_t = y_tan_t * y_cos_t
    y = y0 + b*y_sin_t*np.cos(theta) + a*y_cos_t*np.sin(theta)

    return np.sort([-x, x]), np.sort([-y, y])

您现在可以将原始功能修改为以下内容:

def make_ellipse(x, x0, y, y0, theta, a, b):
    c = np.cos(theta)
    s = np.sin(theta)
    a2 = a**2
    b2 = b**2
    x_box, y_box = ellipse_bounding_box(x0, y0, theta, a, b)
    indices = ((x >= x_box[0]) & (x <= x_box[1]) & 
               (y >= y_box[0]) & (y <= y_box[1]))
    xnew = x[indices] - x0
    ynew = y[indices] - y0
    ellipse = np.zeros_like(x, dtype=np.bool)
    ellipse[indices] = ((xnew * c + ynew * s)**2/a2 +
                        (xnew * s - ynew * c)**2/b2 <= 1)
    return ellipse

答案 2 :(得分:2)

由于除x和y之外的所有内容都是整数,因此可以尝试最小化数组计算的数量。我想大部分时间都花在这个陈述上:

ellipse = (xnew * c + ynew * s)**2/a2 + (xnew * s - ynew * c)**2/b2 <= 1

这样的简单重写应该减少数组操作的次数:

a = float(a)
b = float(b)
ellipse = (xnew * (c/a) + ynew * (s/a))**2 + (xnew * (s/b) - ynew * (c/b))**2 <= 1

12个阵列操作现在是10个(加上4个标量操作)。我不确定numba的jit是否会试过这个。它可能只是首先进行所有广播,然后点击结果操作。在这种情况下,重新排序如此常见的操作会立即完成应该有所帮助。

继续,您可以再次将其重写为

ellipse = ((xnew + ynew * (s/c)) * (c/a))**2 + ((xnew * (s/c) - ynew) * (c/b))**2 <= 1

或者

t = numpy.tan(theta)
ellipse = ((xnew + ynew * t) * (b/a))**2 + (xnew * t - ynew)**2 <= (b/c)**2

用标量替换另一个数组操作,并消除其他标量操作以获得9个数组操作和2个标量操作。

与往常一样,请注意输入范围以避免舍入错误。

不幸的是,如果两个加数中的任何一个大于比较的右手边,那么就没有办法做好运行总和并提前保释。这将是一个明显的加速,但你需要cython(或c / c ++)来编码。

答案 3 :(得分:0)

使用Cython可以大大加快速度。有关如何执行此操作的非常好的文档。