【问题标题】:Optimizing matrix writes in python/numpy在 python/numpy 中优化矩阵写入
【发布时间】:2014-01-13 16:46:21
【问题描述】:

我目前正在尝试优化一段代码,其要点是我们通过计算一堆值并将它们写入矩阵。计算顺序无关紧要:

mat =  np.zeros((n, n))
mat.fill(MAX_VAL)
for i in xrange(0, smallerDim):
    for j in xrange(0,n):
        similarityVal = doACalculation(i,j, data, cache)
        mat[i][j] = abs(1.0 / (similarityVal + 1.0))

我分析了这段代码,发现大约 90% 的时间都花在了将值写回矩阵(最后一行)

我想知道进行此类计算以优化写入的最佳方法是什么。我是否应该写入中间缓冲区并在整行中复制等等。我对性能调整或 numpy 内部结构有点无能。

编辑: doACalculation 不是无副作用的函数。它接收一些数据(假设这是一些 python 对象)以及它写入和读取一些中间步骤的缓存。我不确定它是否可以轻松矢量化。我尝试按照推荐使用 numpy.vectorize ,但没​​有看到比天真的 for 循环有显着的加速。 (我通过状态变量传入了附加数据):

【问题讨论】:

  • 试试numpy.vectorize
  • doACalculation 是什么?
  • 作为一般评论,使用mat[i,j] 而不是mat[i][j] 可以获得更好的性能,但与使用numpy 广播和ufunc 相比,这种改进会很小。

标签: python performance optimization numpy matrix


【解决方案1】:

将其包装在 numba autojit 中应该会大大提高性能。

def doACalculationVector(n, smallerDim):
    return np.ones((smallerDim, n)) + 1


def testVector():
    n = 1000
    smallerDim = 800
    mat =  np.zeros((n, n))
    mat.fill(10) 
    mat[:smallerDim] = abs(1.0 / (doACalculationVector(n, smallerDim) + 1.0))
    return mat

@numba.autojit
def doACalculationNumba(i,j):
    return 2

@numba.autojit
def testNumba():
    n = 1000
    smallerDim = 800
    mat =  np.zeros((n, n))
    mat.fill(10)
    for i in xrange(0, smallerDim):
        for j in xrange(0, n):
            mat[i,j] = abs(1.0 / (doACalculationNumba(i, j) + 1.0))
    return mat

参考原作时间:(mat[i][j]改为mat[i,j]

In [24]: %timeit test()
1 loops, best of 3: 226 ms per loop

现在我稍微简化了这个功能,因为这就是提供的全部内容。但是 testNumba 在计时时大约是 40 倍于 test。并且大约是矢量化的 3 倍

In [20]: %timeit testVector()
100 loops, best of 3: 17.9 ms per loop

In [21]: %timeit testNumba()
100 loops, best of 3: 5.91 ms per loop

【讨论】:

  • 因为 OP 要求使用 numpy 进行计算的最佳方法。这肯定会通过向量化计算来完成,而不是通过使用 jit 编译器获得 2 倍的加速。
  • 顺便说一句,对代码稍作改动就会带来显着的改进。对我来说,您的原始代码分别为纯 python 和 numba 提供了 629 毫秒和 321 毫秒。如果将mat[i][j] = ... 更改为mat[i,j] = ...,则时间更改为 295 毫秒,而 python 与 jit'd 的时间为 9 毫秒。一般来说,无论您使用的是直接 numpy 还是 numba,您都应该始终使用 [i,j,k][i][j][k] 来索引 numpy 数组。
  • @jterrace 不知道“doACalculation”函数的作用,我们无法知道该函数是否容易\可能向量化。 numba autojit 总是会带来一些性能提升,而且它是单行的,所以它基本上是免费的。
  • @JoshAdel 谢谢,我添加了更改。
  • 如果它比紧随其后的线快,至少不太可能太复杂。
【解决方案2】:

如果你能矢量化doACalculation,任务就变得简单了:

similarityArray = doACalculation(np.indices((smallerDim, n)))
mat[:smallerDim] = np.abs(1.0 / (similarityArray + 1))

假设您正确矢量化doACalculation,这应该至少快一个数量级。通常,在使用 NumPy 数组时,您希望尽可能避免显式循环和元素访问。

作为参考,一个可能的doACalculation 的矢量化示例:

# Unvectorized
def doACalculation(i, j):
    return i**2 + i*j + j

# Vectorized
def doACalculation(input):
    i, j = input
    return i**2 + i*j + j

# Vectorized, but with the original call signature
def doACalculation(i, j):
    return i**2 + i*j + j

是的,最后一个版本确实应该与未矢量化的函数相同。有时候就是这么简单。

【讨论】:

    【解决方案3】:

    即使你不能矢量化doACalculation()。您可以使用numpy.vectorize() 加快计算速度。这是测试。

    import numpy as np
    n = 1000
    smallerDim = 500
    
    def doACalculation(i, j):
        return i+j
    

    for循环版本:

    %%timeit
    mat =  np.zeros((n, n))
    
    for i in xrange(0, smallerDim):
        for j in xrange(0,n):
            similarityVal = doACalculation(i,j)
            mat[i,j] = abs(1.0 / (similarityVal + 1.0))
    

    输出:

    1 loops, best of 3: 183 ms per loop
    

    vectorize()版本:

    %%timeit
    mat2 =  np.zeros((n, n))
    i, j = np.ix_(np.arange(smallerDim), np.arange(n))
    f = np.vectorize(doACalculation, "d")
    mat2[:smallerDim] = np.abs(1.0/(f(i, j) + 1))
    

    输出:

    10 loops, best of 3: 97.3 ms per loop
    

    测试结果:

    np.allclose(mat,mat2)
    

    输出:

    True
    

    这种方法并没有让doACalculation()的调用速度快很多,但是它使得后续的计算可以向量化。

    【讨论】:

    • 我正在进一步调查。需要注意的一点是 doACalculation 依赖于 i 和 j 之外的一些额外状态(一些其他数据元素)......有没有办法传递这些信息?
    猜你喜欢
    • 2015-10-02
    • 1970-01-01
    • 2020-03-23
    • 2013-07-06
    • 1970-01-01
    • 2022-08-13
    • 1970-01-01
    • 2017-06-28
    • 1970-01-01
    相关资源
    最近更新 更多