SWIG 内存释放¶
日期 | 2012-04-22(最后修改),2008-12-30(创建) |
---|
简介¶
食谱描述¶
此食谱描述了在相应的 Python numpy 数组对象被销毁时,自动释放通过 `malloc()` 调用在 C 中分配的内存块。该食谱使用 SWIG 和一个修改后的 `numpy.i` 辅助文件。
更具体地说,在现有的 `numpy.i` 中添加了新的片段来处理自动释放数组的内存,这些数组的大小事先未知。与原始片段一样,可以通过调用 `PyArray_SimpleNewFromData()` 将 `malloc()` 内存块转换为返回的 numpy python 对象。但是,返回的 python 对象是使用 `PyCObject_FromVoidPtr()` 创建的,这确保了当 Python 对象被销毁时,分配的内存会自动释放。下面的示例展示了如何使用这些新片段来避免内存泄漏。
由于新的片段基于 `_ARGOUTVIEW_` 片段,因此选择了名称 `_ARGOUTVIEWM_`,其中 `M` 代表托管。所有托管片段(ARRAY1、2 和 3、FARRAY1、2 和 3)都已实现,并且现在已通过了广泛的测试。
从哪里获取文件¶
目前,修改后的 numpy.i 文件可在此处获得(最后更新于 2012-04-22):* http://ezwidgets.googlecode.com/svn/trunk/numpy/numpy.i * http://ezwidgets.googlecode.com/svn/trunk/numpy/pyfragments.swg
代码是如何产生的¶
原始的内存释放代码由 Travis Oliphant 编写(参见 http://blog.enthought.com/?p=62 ),据我所知,这些聪明的人是第一个在 swig 文件中使用它的人(参见 http://niftilib.sourceforge.net/pynifti,文件 nifticlib.i)。Lisandro Dalcin 随后指出了一种使用 CObjects 的简化实现,Travis 在这篇 更新的博客文章 中详细介绍了它。
如何使用新的片段¶
重要步骤¶
在 yourfile.i 中,%init 函数使用您已经知道的相同的 `import_array()` 调用
%init %{
import_array();
%}
... 然后只需使用 ARGOUTVIEWM_ARRAY1 而不是 ARGOUTVIEW_ARRAY1,内存释放将在 Python 数组销毁时自动处理(参见下面的示例)。
一个简单的 ARGOUTVIEWM_ARRAY1 示例¶
SWIG 封装的 C 函数使用 `malloc()` 为 N 个整数数组分配内存。从 Python 中,此函数被重复调用,并且创建的数组被销毁(M 次)。
使用 numpy.i 中提供的 ARGOUTVIEW_ARRAY1,这将导致内存泄漏(我知道 ARGOUTVIEW_ARRAY1 不是为此目的设计的,但这很诱人!)。
使用 ARGOUTVIEWM_ARRAY1 片段,使用 `malloc()` 分配的内存将在数组删除时自动释放。
Python 测试程序使用 ARGOUTVIEW_ARRAY1 和 ARGOUTVIEWM_ARRAY1 创建和删除 1024\^2 个 int 数组 2048 次,当内存分配失败时,C 中会生成一个异常,并在 Python 中捕获,显示最终导致分配失败的迭代次数。
C 源代码(ezalloc.c 和 ezalloc.h)¶
这是 ezalloc.h 文件
void alloc(int ni, int** veco, int *n);
这是 ezalloc.c 文件
#include <stdio.h>
#include <errno.h>
#include "ezalloc.h"
void alloc(int ni, int** veco, int *n)
{
int *temp;
temp = (int *)malloc(ni*sizeof(int));
if (temp == NULL)
errno = ENOMEM;
//veco is either NULL or pointing to the allocated block of memory...
*veco = temp;
*n = ni;
}
接口文件(ezalloc.i)¶
该文件(可在此处获取:ezalloc.i)做了一些有趣的事情:* 正如我在引言中所说,在 `%init` 部分调用 `import_array()` 函数现在还会初始化内存释放代码。这里没有其他需要添加的内容。* 如果内存分配失败,则会生成一个异常。在代码结构的几次迭代之后,使用 `errno` 和 `SWIG_fail` 是我想到的最简单的方法。* 在此示例中,创建了两个内联函数,一个使用 ARGOUTVIEW_ARRAY1,另一个使用 ARGOUTVIEWM_ARRAY1。这两个函数都使用 `alloc()` 函数(参见 ezalloc.h 和 ezalloc.c)。
%module ezalloc
%{
#include <errno.h>
#include "ezalloc.h"
#define SWIG_FILE_WITH_INIT
%}
%include "numpy.i"
%init %{
import_array();
%}
%apply (int** ARGOUTVIEWM_ARRAY1, int *DIM1) {(int** veco1, int* n1)}
%apply (int** ARGOUTVIEW_ARRAY1, int *DIM1) {(int** veco2, int* n2)}
%include "ezalloc.h"
%exception
{
errno = 0;
$action
if (errno != 0)
{
switch(errno)
{
case ENOMEM:
PyErr_Format(PyExc_MemoryError, "Failed malloc()");
break;
default:
PyErr_Format(PyExc_Exception, "Unknown exception");
}
SWIG_fail;
}
}
%rename (alloc_managed) my_alloc1;
%rename (alloc_leaking) my_alloc2;
%inline %{
void my_alloc1(int ni, int** veco1, int *n1)
{
/* The function... */
alloc(ni, veco1, n1);
}
void my_alloc2(int ni, int** veco2, int *n2)
{
/* The function... */
alloc(ni, veco2, n2);
}
%}
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# alloc extension module
_ezalloc = Extension("_ezalloc",
["ezalloc.i","ezalloc.c"],
include_dirs = [numpy_include],
extra_compile_args = ["--verbose"]
)
# NumyTypemapTests setup
setup( name = "alloc functions",
description = "Testing managed arrays",
author = "Egor Zindy",
version = "1.0",
ext_modules = [_ezalloc]
)
编译模块¶
设置命令行如下(在 Windows 上,使用 mingw)
$> python setup_alloc.py build --compiler=mingw32
或者在 UN*X 上,只需
$> python setup_alloc.py build
测试模块¶
如果一切按计划进行,在 `build\lib.XXX` 目录中应该有一个 `_ezalloc.pyd` 文件。该文件需要复制到包含 `ezalloc.py` 文件的目录中(由 swig 生成)。
SVN 存储库中提供了一个 Python 测试程序 (test_alloc.py),并在下面复制
import ezalloc
n = 2048
# this multiplied by sizeof(int) to get size in bytes...
#assuming sizeof(int)=4 on a 32bit machine (sorry, it's late!)
m = 1024 * 1024
err = 0
print "ARGOUTVIEWM_ARRAY1 (managed arrays) - %d allocations (%d bytes each)" % (n,4*m)
for i in range(n):
try:
#allocating some memory
a = ezalloc.alloc_managed(m)
#deleting the array
del a
except:
err = 1
print "Step %d failed" % i
break
if err == 0:
print "Done!\n"
print "ARGOUTVIEW_ARRAY1 (unmanaged, leaking) - %d allocations (%d bytes each)" % (n,4*m)
for i in range(n):
try:
#allocating some memory
a = ezalloc.alloc_leaking(m)
#deleting the array
del a
except:
err = 1
print "Step %d failed" % i
break
if err == 0:
print "Done? Increase n!\n"
然后,一个
$> python test_alloc.py
将产生类似于此的输出
ARGOUTVIEWM_ARRAY1 (managed arrays) - 2048 allocations (4194304 bytes each)
Done!
ARGOUTVIEW_ARRAY1 (unmanaged, leaking) - 2048 allocations (4194304 bytes each)
Step 483 failed
每次数组视图被删除时,未管理的数组都会泄漏内存。管理的数组将无缝地删除内存块。这在 Windows XP 和 Linux 上都经过了测试。
一个简单的 ARGOUTVIEWM_ARRAY2 示例¶
以下示例展示了如何从 C 返回一个二维数组,该数组也受益于自动内存释放。
一个简单的“裁剪”函数使用 SWIG/numpy.i 包装,并返回输入数组的切片。当用 `array_out = crop.crop(array_in, d1_0,d1_1, d2_0,d2_1)` 使用时,它等效于本机 numpy 切片 `array_out = array_in[d1_0:d1_1, d2_0:d2_1]`。
C 源代码 (crop.c 和 crop.h)¶
这是 crop.h 文件
void crop(int *arr_in, int dim1, int dim2, int d1_0, int d1_1, int d2_0, int d2_1, int **arr_out, int *dim1_out, int *dim2_out);
这是 crop.c 文件
#include <stdlib.h>
#include <errno.h>
#include "crop.h"
void crop(int *arr_in, int dim1, int dim2, int d1_0, int d1_1, int d2_0, int d2_1, int **arr_out, int *dim1_out, int *dim2_out)
{
int *arr=NULL;
int dim1_o=0;
int dim2_o=0;
int i,j;
//value checks
if ((d1_1 < d1_0) || (d2_1 < d2_0) ||
(d1_0 >= dim1) || (d1_1 >= dim1) || (d1_0 < 0) || (d1_1 < 0) ||
(d2_0 >= dim2) || (d2_1 >= dim2) || (d2_0 < 0) || (d2_1 < 0))
{
errno = EPERM;
goto end;
}
//output sizes
dim1_o = d1_1-d1_0;
dim2_o = d2_1-d2_0;
//memory allocation
arr = (int *)malloc(dim1_o*dim2_o*sizeof(int));
if (arr == NULL)
{
errno = ENOMEM;
goto end;
}
//copying the cropped arr_in region to arr (naive implementation)
printf("\n--- d1_0=%d d1_1=%d (rows) -- d2_0=%d d2_1=%d (columns)\n",d1_0,d1_1,d2_0,d2_1);
for (j=0; j<dim1_o; j++)
{
for (i=0; i<dim2_o; i++)
{
arr[j*dim2_o+i] = arr_in[(j+d1_0)*dim2+(i+d2_0)];
printf("%d ",arr[j*dim2_o+i]);
}
printf("\n");
}
printf("---\n\n");
end:
*dim1_out = dim1_o;
*dim2_out = dim2_o;
*arr_out = arr;
}
%module crop
%{
#include <errno.h>
#include "crop.h"
#define SWIG_FILE_WITH_INIT
%}
%include "numpy.i"
%init %{
import_array();
%}
%exception crop
{
errno = 0;
$action
if (errno != 0)
{
switch(errno)
{
case EPERM:
PyErr_Format(PyExc_IndexError, "Index error");
break;
case ENOMEM:
PyErr_Format(PyExc_MemoryError, "Not enough memory");
break;
default:
PyErr_Format(PyExc_Exception, "Unknown exception");
}
SWIG_fail;
}
}
%apply (int* IN_ARRAY2, int DIM1, int DIM2) {(int *arr_in, int dim1, int dim2)}
%apply (int** ARGOUTVIEWM_ARRAY2, int* DIM1, int* DIM2) {(int **arr_out, int *dim1_out, int *dim2_out)}
%include "crop.h"
#! /usr/bin/env python
# System imports
from distutils.core import *
from distutils import sysconfig
# Third-party modules - we depend on numpy for everything
import numpy
# Obtain the numpy include directory. This logic works across numpy versions.
try:
numpy_include = numpy.get_include()
except AttributeError:
numpy_include = numpy.get_numpy_include()
# crop extension module
_crop = Extension("_crop",
["crop.i","crop.c"],
include_dirs = [numpy_include],
extra_compile_args = ["--verbose"]
)
# NumyTypemapTests setup
setup( name = "crop test",
description = "A simple crop test to demonstrate the use of ARGOUTVIEWM_ARRAY2",
author = "Egor Zindy",
version = "1.0",
ext_modules = [_crop]
)
测试模块¶
如果一切按计划进行,在 `build\lib.XXX` 目录中应该有一个 `_crop.pyd` 文件。该文件需要复制到包含 `crop.py` 文件的目录中(由 swig 生成)。
SVN 存储库中提供了一个 Python 测试程序 (test_crop.py),并在下面复制
import crop
import numpy
a = numpy.zeros((5,10),numpy.int)
a[numpy.arange(5),:] = numpy.arange(10)
b = numpy.transpose([(10 ** numpy.arange(5))])
a = (a*b)[:,1:] #this array is most likely NOT contiguous
print a
print "dim1=%d dim2=%d" % (a.shape[0],a.shape[1])
d1_0 = 2
d1_1 = 4
d2_0 = 1
d2_1 = 5
c = crop.crop(a, d1_0,d1_1, d2_0,d2_1)
d = a[d1_0:d1_1, d2_0:d2_1]
print "returned array:"
print c
print "native slicing:"
print d
输出如下所示
$ python test_crop.py
[[ 1 2 3 4 5 6 7 8 9]
[ 10 20 30 40 50 60 70 80 90]
[ 100 200 300 400 500 600 700 800 900]
[ 1000 2000 3000 4000 5000 6000 7000 8000 9000]
[10000 20000 30000 40000 50000 60000 70000 80000 90000]]
dim1=5 dim2=9
--- d1_0=2 d1_1=4 (rows) -- d2_0=1 d2_1=5 (columns)
200 300 400 500
2000 3000 4000 5000
---
returned array:
[[ 200 300 400 500]
[2000 3000 4000 5000]]
native slicing:
[[ 200 300 400 500]
[2000 3000 4000 5000]]
numpy.i 负责在需要时使数组连续,因此唯一需要处理的是数组方向。
结论和评论¶
各位再见!文件可在 [http://code.google.com/p/ezwidgets/source/browse/#svn/trunk/numpy Google 代码 SVN] 上获取。 欢迎评论!
此致,Egor
章节作者:EgorZindy