我正在尝试优化我的python代码。当
我试图根据每个元素的值对numpy数组应用一个函数。例如,我有一个包含1000个元素的数组,我对大于公差的值应用一个函数,对其余的值应用另一个函数(泰勒级数)。我做了掩蔽,但仍然很慢,至少我调用了6400万次以下函数。

EPSILONZETA = 1.0e-6
ZETA1_12 = 1.0/12.0
ZETA1_720 = 1.0/720.0

def masked_condition_zero(array, tolerance):
    """ Return the indices where values are lesser (and greater) than tolerance
    """
    # search indices where array values < tolerance
    indzeros_ = np.where(np.abs(array) < tolerance)[0]

    # create mask
    mask_ = np.ones(np.shape(array), dtype=bool)

    mask_[[indzeros_]] = False

    return (~mask_, mask_)

def bernoulli_function1(zeta):
    """ Returns the Bernoulli function of zeta, vector version
    """
    # get the indices according to condition
    zeros_, others_ = masked_condition_zero(zeta, EPSILONZETA)

    # create an array filled with zeros
    fb_ = np.zeros(np.shape(zeta))

    # Apply the original function to the values greater than EPSILONZETA
    fb_[others_] = zeta[others_]/(np.exp(zeta[others_])-1.0)

    # computes series for zeta < eps
    zeta0_ = zeta[zeros_]
    zeta2_ = zeta0_ *  zeta0_
    zeta4_ =  zeta2_ * zeta2_
    fb_[zeros_] = 1.0 - 0.5*zeta0_ + ZETA1_12 * zeta2_ - ZETA1_720 * zeta4_
    return fb_

现在假设您有一个带有正负浮动的数组zeta,它在每个循环中变化,并扩展到2^26次迭代,您希望每次都计算fbernoulli_function1(zeta)。
有更好的解决办法吗?

最佳答案

问题的基本结构是:

def foo(zeta):
    result = np.empty_like(zeta)
    I = condition(zeta)
    nI = ~I
    result[I] = func1(zeta[I])
    result[nI] = func2(zeta[nI])

看起来多项式表达式完全可以计算zeta,但这是“异常”,即zeta太接近0时的回退计算。
如果两个函数都可以计算为zeta,则可以使用以下位置:
np.where(condition(zeta), func1(zeta), func2(zeta))

这是精简版:
def foo(zeta):
    result = np.empty_like(zeta)
    I = condition(zeta)
    nI = ~I
    v1 = func1(zeta)
    v2 = func2(zeta)
    result[I] = v1[I]
    result[nI] = v2[nI]

另一种选择是将一个函数应用于所有值,而另一个只应用于“异常”。
def foo(zeta):
    result = func2(zeta)
    I = condition(zeta)
    result[I] = func1[zeta[I]]

当然,反过来-result = func1(zeta); result[nI]=func2[zeta]
在我简短的时间测试中,func1func2所花的时间差不多相同。
masked_condition_zero也需要时间,但更简单的np.abs(array) < tolerance(它是~J)会将其减半。
让我们比较一下分配策略
def foo(zeta, J, nJ):
    result = np.empty_like(zeta)
    result[J] = fun1(zeta[J])
    result[nJ] = fun2(zeta[nJ])
    return result

对于zeta[J]为全部zeta的10%的样本,一些样本时间为:
In [127]: timeit foo(zeta, J, nJ)
10000 loops, best of 3: 55.7 µs per loop

In [128]: timeit result=fun2(zeta); result[J]=fun1(zeta[J])
10000 loops, best of 3: 49.2 µs per loop

In [129]: timeit np.where(J, fun1(zeta),fun2(zeta))
10000 loops, best of 3: 73.4 µs per loop

In [130]: timeit result=fun1(zeta); result[nJ]=fun2(zeta[nJ])
10000 loops, best of 3: 60.7 µs per loop

第二种情况是最快的,因为在较少的值上运行fun1可以补偿索引zeta[J]所增加的成本。索引成本和函数评估成本之间存在权衡。像这样的布尔索引比切片更昂贵。随着其他价值观的融合,时间安排可能会走向另一个方向。
这看起来像是一个问题,你可以消磨时间,但我没有看到任何突破,虽然有可能削减一个数量级的时间。

08-04 20:26