Pygame + Numpy 应用程序优化内存使用

64 阅读3分钟

该程序使用 PyGame 加载两张图片,并将它们转换为 Numpy 数组,然后执行一些其他 Numpy 操作(例如 FFT)来输出最终结果(一些数字)。输入可以很大,但在任何时刻只应该有一个或两个大对象处于活动状态。

huake_00198_.jpg 测试图像约为 1000 万像素,灰度化后转换为 10MB。它被转换为数据类型为 uint8 的 Numpy 数组,经过一些处理(应用 Hamming 窗口)后,变为数据类型为 float64 的数组。这样会将两张图片加载到数组中;后来的 FFT 步骤会产生数据类型为 complex128 的数组。在添加过多的 gc.collect 调用之前,程序内存大小往往会随着每个步骤而增加。此外,似乎大多数 Numpy 操作都会给出最高精度的结果。

在我的 1GB Linux 机器上运行测试(不使用 gc.collect 调用)会导致长时间的抖动,我还没有等到它结束。我还没有详细的内存使用统计数据——我尝试了一些 Python 模块和 time 命令,但没有用;现在我正在研究 valgrind。观察 PS(并在测试的后期处理机器无响应的情况)表明最大内存使用量约为 800 MB。

一个包含 1000 万个单元格的 complex128 数组应该占 160 MB。理想情况下,最多同时有两个这样的数组处于活动状态,加上不太重要的 Python 和 Numpy 库以及其他东西,可能意味着允许使用 500 MB。

我能想到两种解决这个问题的角度:

  • 尽快丢弃中间数组。这就是 gc.collect 调用所做的——它们似乎改善了情况,因为现在只需几分钟就能完成抖动 ;-)。我认为人们可以预料,在像 Python 这样的语言中进行内存密集型编程将需要一些手动干预。
  • 在每个步骤中使用精度较低的 Numpy 数组。不幸的是,像 fft2 这样的返回数组的操作似乎不允许指定类型。

所以我的主要问题是:有没有办法在 Numpy 数组操作中指定输出精度?

更一般地说,在使用 Numpy 时还有其他常见的节省内存的技术吗?

此外,Numpy 有没有更惯用的方法来释放数组内存?(我认为这会让数组对象在 Python 中保持活动状态,但处于不可用状态。)显式删除后立即进行 GC 让人觉得很别扭。

import sys
import numpy
import pygame
import gc


def get_image_data(filename):
    im = pygame.image.load(filename)
    im2 = im.convert(8)
    a = pygame.surfarray.array2d(im2)
    hw1 = numpy.hamming(a.shape[0])
    hw2 = numpy.hamming(a.shape[1])
    a = a.transpose()
    a = a*hw1
    a = a.transpose()
    a = a*hw2
    return a


def check():
    gc.collect()
    print 'check'


def main(args):
    pygame.init()

    pygame.sndarray.use_arraytype('numpy')

    filename1 = args[1]
    filename2 = args[2]
    im1 = get_image_data(filename1)
    im2 = get_image_data(filename2)
    check()
    out1 = numpy.fft.fft2(im1)
    del im1
    check()
    out2 = numpy.fft.fft2(im2)
    del im2
    check()
    out3 = out1.conjugate() * out2
    del out1, out2
    check()
    correl = numpy.fft.ifft2(out3)
    del out3
    check()
    maxs = correl.argmax()
    maxpt = maxs % correl.shape[0], maxs / correl.shape[0]
    print correl[maxpt], maxpt, (correl.shape[0] - maxpt[0], correl.shape[1] - maxpt[1])


if __name__ == '__main__':
    args = sys.argv
    exit(main(args))


2、解决方案

  • 使用 Scipy 中的 ndimage 模块。这个模块专用于计算图像之间的卷积,可能比通过傅里叶变换进行“手动”方法更有效。最好尝试使用它,而不是通过 Numpy。

  • 使用 SciPy 0.8.0 beta 1。它对大多数 fft 代码提供单精度支持。