5

ctypes を介していくつかの C コードをラップしようとしています。私のコード (以下に添付) は機能していますが、memory_profilerは、どこかでメモリ リークが発生していることを示唆しています。ラップしようとしている基本的な C 構造体は、「image.h」で定義されています。これは、データへのポインター、ポインター配列 (ここに含まれていない他のさまざまな関数に必要)、およびいくつかの形状情報を含む画像オブジェクトを定義します。

image.h :

#include <stdio.h>
#include <stdlib.h>

typedef struct image {
double * data;    /*< The main pointer to the image data*/
i3_flt **row;     /*< An array of pointers to each row of the image*/
unsigned long n;  /*< The total number of pixels in the image*/
unsigned long nx; /*< The number of pixels per row (horizontal image dimensions)*/
unsigned long ny; /*< The number of pixels per column (vertical image dimensions)*/
} image;

この C 構造体を ctypes でラップする Python コードは、以下の「image_wrapper.py」に含まれています。Python クラスのImageは、ここには含めなかった多くのメソッドを実装しています。アイデアは、python オブジェクトを持つことです。これは、numpy 配列と同じくらい便利に使用できます。実際、このクラスには、C 構造体内のデータ ポインターとまったく同じメモリ位置を指す属性 (self.array) として numpy 配列が含まれています。

image_wrapper.py :

import numpy
import ctypes as c

class Image(object):

    def __init__(self, nx, ny):

        self.nx = nx
        self.ny = ny
        self.n = nx * ny
        self.shape = tuple((nx, ny))
        self.array = numpy.zeros((nx, ny), order='C', dtype=c.c_double)
        self._argtype = self._argtype_generator()
        self._update_cstruct_from_array()

    def _update_cstruct_from_array(self):

        data_pointer = self.array.ctypes.data_as(c.POINTER(c.c_double))

        ctypes_pointer = c.POINTER(c.c_double) * self.ny
        row_pointers = ctypes_pointer(
            *[self.array[i,:].ctypes.data_as(c.POINTER(c.c_double)) for i in range(self.ny)])

        ctypes_pointer = c.POINTER(ctypes_pointer)
        row_pointer = ctypes_pointer(row_pointers)

        self._cstruct = c.pointer(self._argtype(data=data_pointer,
                                                row=row_pointer,
                                                n=self.n,
                                                nx=self.nx,
                                                ny=self.ny))

    def _argtype_generator(self):

        class _Argtype(c.Structure):
            _fields_ = [("data", c.POINTER(c.c_double)),
                        ("row", c.POINTER(c.POINTER(c.c_double) * self.ny)),
                        ("n", c.c_ulong),
                        ("nx", c.c_ulong),
                        ("ny", c.c_ulong)]

        return _Argtype

上記のコードのメモリ消費量を memory_profiler でテストすると、Python のガベージ コレクタがすべての参照をクリーンアップできないことがわかります。これは、さまざまなサイズのループ内に可変数のクラス インスタンスを作成するテスト コードです。

test_image_wrapper.py

import sys
import image_wrapper as img
import numpy as np 

@profile
def main(argv):
    image_size = 500

    print 'Create 10 images\n'
    for i in range(10):
        x = img.Image(image_size, image_size)
        del x

    print 'Create 100 images\n'
    for i in range(100):
        x = img.Image(image_size, image_size)
        del x

    print 'Create 1000 images\n'
    for i in range(1000):
        x = img.Image(image_size, image_size)
        del x

    print 'Create 10000 images\n'
    for i in range(10000):
        x = img.Image(image_size, image_size)
        del x

if __name__ == "__main__":
    main(sys.argv)

@profile は、memory_profiler に後続の関数 (ここではメイン) を分析するように指示しています。test_image_wrapper.py で memory_profiler を使用して python を実行する

python -m memory_profiler test_image_wrapper.py

次の出力が得られます。

Filename: test_image_wrapper.py

Line #    Mem usage    Increment   Line Contents
================================================
    49                             @profile
    50                             def main(argv):
    51                                 """
    52                                 Script to test memory usage of image.py
    53    16.898 MB     0.000 MB       """
    54    16.898 MB     0.000 MB       image_size = 500
    55                             
    56    16.906 MB     0.008 MB       print 'Create 10 images\n'
    57    19.152 MB     2.246 MB       for i in range(10):
    58    19.152 MB     0.000 MB           x = img.Image(image_size, image_size)
    59    19.152 MB     0.000 MB           del x
    60                             
    61    19.152 MB     0.000 MB       print 'Create 100 images\n'
    62    19.512 MB     0.359 MB       for i in range(100):
    63    19.516 MB     0.004 MB           x = img.Image(image_size, image_size)
    64    19.516 MB     0.000 MB           del x
    65                             
    66    19.516 MB     0.000 MB       print 'Create 1000 images\n'
    67    25.324 MB     5.809 MB       for i in range(1000):
    68    25.328 MB     0.004 MB           x = img.Image(image_size, image_size)
    69    25.328 MB     0.000 MB           del x
    70                             
    71    25.328 MB     0.000 MB       print 'Create 10000 images\n'
    72    83.543 MB    58.215 MB       for i in range(10000):
    73    83.543 MB     0.000 MB           x = img.Image(image_size, image_size)
    74                                     del x

Python 内のクラス Image の各インスタンスは、約 5 ~ 6kB を残しているようで、10k の画像を処理すると合計で最大 58MB になります。個々のオブジェクトの場合、これはそれほど多くないように思えますが、1,000 万で実行する必要があるため、気にします。リークの原因と思われる行は、image_wrapper.py に含まれる次の行です。

        self._cstruct = c.pointer(self._argtype(data=data_pointer,
                                                row=row_pointer,
                                                n=self.n,
                                                nx=self.nx,
                                                ny=self.ny))

上記のように、Python のガベージ コレクターはすべての参照をクリーンアップできないようです。私は独自のdel関数を実装しようとしました。

def __del__(self):
    del self._cstruct
    del self

残念ながら、これは問題を解決していないようです。いくつかのメモリ デバッガーの調査と試行に 1 日を費やした後、私の最後の手段はスタック オーバーフローのようです。貴重なご意見やご提案をいただき、誠にありがとうございます。

4

3 に答える 3

0

Python 2.7.3 と Numpy バ​​ージョン 1.6.1 を使用しています。

犯人はこの行だと思います...

self.array = numpy.zeros((nx, ny), order='C', dtype=c.c_double)

このチケットによると、バージョン 1.6 でメモリ リークがありnumpy.zeroes()、バージョン 1.7 で修正されたようです。

于 2013-06-15T17:32:07.830 に答える