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 日を費やした後、私の最後の手段はスタック オーバーフローのようです。貴重なご意見やご提案をいただき、誠にありがとうございます。