【问题标题】:Need help vectorizing code or optimizing需要帮助矢量化代码或优化
【发布时间】:2013-07-08 14:33:53
【问题描述】:

我正在尝试通过首先对数据进行插值来制作一个曲面来进行双积分。我正在使用 numba 来尝试加快这个过程,但这需要的时间太长了。

Here is my code, 以及运行位于herehere 的代码所需的图像。

【问题讨论】:

  • 现在需要多长时间?什么是可接受的结果?
  • 嗯,嵌套 for 循环超过 30 秒。所以我在 0,1 上 30,然后在 0,2 上 30 等。它是一个 2000x2000 矩阵,因此需要数年才能运行。因此,如果它甚至可以在几天内运行,那将是惊人的。真的只是找更短的
  • 在没有 Numba 的 Macbook Air 1.6 GHz i5 上,每次迭代需要 340 秒。

标签: python optimization numba


【解决方案1】:

注意到您的代码有一组四重嵌套的 for 循环,我专注于优化内部对。这是旧代码:

for i in xrange(K.shape[0]):
    for j in xrange(K.shape[1]):

        print(i,j)
        '''create an r vector '''
        r=(i*distX,j*distY,z)

        for x in xrange(img.shape[0]):
            for y in xrange(img.shape[1]):
                '''create an ksi vector, then calculate
                   it's norm, and the dot product of r and ksi'''
                ksi=(x*distX,y*distY,z)
                ksiNorm=np.linalg.norm(ksi)
                ksiDotR=float(np.dot(ksi,r))

                '''calculate the integrand'''
                temp[x,y]=img[x,y]*np.exp(1j*k*ksiDotR/ksiNorm)

        '''interpolate so that we can do the integral and take the integral'''
        temp2=rbs(a,b,temp.real)
        K[i,j]=temp2.integral(0,n,0,m)

由于 K 和 img 都在 2000x2000 左右,所以最里面的语句需要执行 16 万亿次。这在使用 Python 时根本不切实际,但我们可以使用 NumPy 将工作转移到 C 和/或 Fortran 中进行向量化。我一次小心地做了这一步,以确保结果匹配;这就是我最终得到的结果:

'''create all r vectors'''
R = np.empty((K.shape[0], K.shape[1], 3))
R[:,:,0] = np.repeat(np.arange(K.shape[0]), K.shape[1]).reshape(K.shape) * distX
R[:,:,1] = np.arange(K.shape[1]) * distY
R[:,:,2] = z

'''create all ksi vectors'''
KSI = np.empty((img.shape[0], img.shape[1], 3))
KSI[:,:,0] = np.repeat(np.arange(img.shape[0]), img.shape[1]).reshape(img.shape) * distX
KSI[:,:,1] = np.arange(img.shape[1]) * distY
KSI[:,:,2] = z

# vectorized 2-norm; see http://stackoverflow.com/a/7741976/4323                                                    
KSInorm = np.sum(np.abs(KSI)**2,axis=-1)**(1./2)

# loop over entire K, which is same shape as img, rows first                                                        
# this loop populates K, one pixel at a time (so can be parallelized)                                               
for i in xrange(K.shape[0]):                                                                                    
    for j in xrange(K.shape[1]):                                                                                

        print(i, j)

        KSIdotR = np.dot(KSI, R[i,j])
        temp = img * np.exp(1j * k * KSIdotR / KSInorm)

        '''interpolate so that we can do the integral and take the integral'''
        temp2 = rbs(a, b, temp.real)
        K[i,j] = temp2.integral(0, n, 0, m)

内部循环对现在完全消失了,取而代之的是预先完成的矢量化操作(空间成本与输入的大小成线性关系)。

这在我的 Macbook Air 1.6 GHz i5 上将外部两个循环的每次迭代时间从 340 秒减少到 1.3 秒,而无需使用 Numba。在每次迭代的 1.3 秒中,有 0.68 秒用于rbs 函数,即scipy.interpolate.RectBivariateSpline。可能还有进一步优化的空间——这里有一些想法:

  1. 重新启用 Numba。我的系统上没有。在这一点上,它可能没有太大区别,但很容易测试。
  2. 进行更多特定领域的优化,例如尝试简化正在完成的基本计算。我的优化旨在无损,我不知道您的问题域,所以我无法尽可能深入地优化。
  3. 尝试向量化剩余的循环。除非您愿意将 scipy RBS 函数替换为支持每次调用多次计算的功能,否则这可能会很困难。
  4. 获得更快的 CPU。我的很慢;只需使用比我的小型笔记本电脑更好的计算机,您就可以将速度提高至少 2 倍。
  5. 对数据进行下采样。您的测试图像为 2000x2000 像素,但包含的细节很少。如果将它们的线性尺寸减少 2-10 倍,您将获得巨大的加速。

所以我现在就这样。这会让你在哪里?假设一台稍微好一点的计算机并且没有进一步的优化工作,即使是优化的代码也需要大约一个月的时间来处理你的测试图像。如果你只需要这样做一次,也许没问题。如果您需要更频繁地执行此操作,或者需要在尝试不同的事情时迭代代码,您可能需要继续优化——从现在消耗一半以上时间的 RBS 函数开始。

额外提示:如果您的代码没有像 kK 这样几乎相同的变量名,也没有使用 j 作为变量名和复杂变量名,那么您的代码将更容易处理数字后缀 (0j)。

【讨论】:

  • 谢谢!我更改了一些名称以使其不那么混乱。我通常会这样做(并添加 cmets),但我非常沮丧。 Numba 不能解决这个问题,得到一个奇怪的 JIT 错误,但无论如何它可能不会加快速度。 Numexpr 可能,而且我应该能够轻松地并行化 for 循环,因为每个像素都是独立的。不幸的是,我不能从照片中丢失任何信息,这是尝试使用 DIHM(数字内联全息显微镜)数字重建全息图.你能想出一个更好的方法来做一个双积分吗?比使用苏格兰皇家银行?这可能会减少时间。
  • 并行处理所有像素应该可以通过多种方式轻松完成——我应该提到这一点。即使只是简单地使用基本的multiprocessing 模块,你的内核数量也应该可以得到几乎线性的加速。我的机器没有很多内核,但是如果你有一台 12 路的机器,你可以在 3 天左右的时间内完成整个工作,而无需进一步优化。关于 RBS,我不是这方面的专家,但令我震惊的是,您花费了大量时间来拟合数据曲线,而只是为了计算积分。如果你直接做黎曼和怎么办?
  • 我实际上将使用 mpi4py,以便可以在具有数百个内核的集群上运行它。多处理不适用于集群(不幸的是)。黎曼和直接是我们最初想要做的,但我们认为为数据创建一个表面将有助于集成。二维黎曼和似乎需要很多时间。另外,感谢您的所有帮助。我从代码中学到了很多东西。
  • 不客气。听起来你走在正确的轨道上——只要继续思考如何简化和减少操作的数量——尤其是像循环这样的纯 Python 操作。 2D 黎曼和可能需要一些时间,但现在有一半的时间花在做替代上,所以黎曼可能会更快,特别是如果你愿意接受一些精度损失(这可能不是之后的损失)所有考虑到您的原始方法都集成了拟合曲线而不是实际数据)。
  • 我现在实际上已经矢量化了您拥有的点积,现在也将它从 for 循环中取出。慢慢地努力摆脱这个for循环,只是在这个过程中遇到了很多内存错误。
猜你喜欢
  • 2013-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 2015-11-20
  • 1970-01-01
  • 2014-12-23
  • 2017-04-12
相关资源
最近更新 更多