Python ctypes 'c_char_p' 内存泄漏
Python ctypes 'c_char_p' memory leak
我正在开发一个 Python 密码学库。我想通过使用 GMP 在 C++ 中编写主要 类 来优化我的库。我编写了 C++ 类 并编写了 extern
方法来使用主要的算术运算:加法、减法等...这些方法 returns 将结果作为 char* 以避免转换问题.我构建了我的库的 DLL,并在带有 ctypes 的 Python 包装器中声明了这些方法。我注意到每次对大量数字进行算术运算后,内存都会呈指数级增长。我一直在寻找我的 C++ 实现中的问题,但由于 C++ 垃圾收集器,没有出现任何问题。我在寻找一个可能的解决方案,所以我发现我必须实现一个 C++ 方法来释放 DLL 创建的字符串的内存。所以我写了这个简单的方法:
extern "C" {
__declspec(dllexport) void free_memory(char * n)
{
free(n);
}
...
}
我在 Python 包装器中实现了这段代码以释放 DLL 分配的内存:
import os
import ctypes
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
NUMERIC = ctypes.CDLL(DIR_PATH + "/numeric.dll")
...
NUMERIC.free_memory.argtypes = [ctypes.c_void_p]
NUMERIC.free_memory.restype = None
def void_cast(n):
a = ctypes.cast(n, ctypes.c_char_p)
res = ctypes.c_char_p(a.value)
NUMERIC.free_memory(a)
return res
因此,对于 res = ctypes.c_char_p (a.value)
,我创建了一个不再指向 a
的新变量。这样我用DLL的方式正确删除了a
,但是还是有内存泄露的问题。就好像 Python 垃圾收集器没有正确释放 c_char_p
类型字符串的内存。在之前的实现中我只使用了 Python 和 gmpy2
库,所以所有的数字都被转换为 mpz
或 mpq
。我使用 memory_profiler
包测试了内存消耗。我创建了 40 个投影点类型的对象,在椭圆曲线上定义,我计算了产品 i*P
,i
从 1 到 40。gmpy2
总共使用了大约 70MB。相反,在 C++ 中使用带有 类 的 ctypes 内存消耗上升到 1.5GB。很明显有问题,尤其是当只有处理算术运算的基数 类 发生变化时。如何在没有内存泄漏问题的情况下正确释放内存?
我举了一个计算算术运算的 extern
方法的例子,但我已经检查过问题只在于通过 free_memory
函数正确释放内存并重新分配字符串,以便垃圾收集器Python 将在需要时释放字符串。
extern "C" {
__declspec(dllexport) const char* rat_add(const char * n, const char * m)
{
return (RationalNum(n) + RationalNum(m)).getValue();
}
}
在此先致谢,祝您有愉快的一天。
PS:很明显,在 C++ 中,我正确地实现了析构函数方法来释放 mpz_t
和 mpq_t
创建的对象的 space。
问题出在这一行:
res = ctypes.c_char_p(a.value)
这将创建 a.value
的副本并将 res
设置为指向该副本的 c_char_p
。但是Python并没有对ctypes
指针做内存管理,所以复制会泄露!
如果将上面的行替换为:
,应该可以解决泄漏问题
res = bytes(memoryview(a.value))
这也会创建一个副本,但是 res
将是一个真正的 Python 对象。
我正在开发一个 Python 密码学库。我想通过使用 GMP 在 C++ 中编写主要 类 来优化我的库。我编写了 C++ 类 并编写了 extern
方法来使用主要的算术运算:加法、减法等...这些方法 returns 将结果作为 char* 以避免转换问题.我构建了我的库的 DLL,并在带有 ctypes 的 Python 包装器中声明了这些方法。我注意到每次对大量数字进行算术运算后,内存都会呈指数级增长。我一直在寻找我的 C++ 实现中的问题,但由于 C++ 垃圾收集器,没有出现任何问题。我在寻找一个可能的解决方案,所以我发现我必须实现一个 C++ 方法来释放 DLL 创建的字符串的内存。所以我写了这个简单的方法:
extern "C" {
__declspec(dllexport) void free_memory(char * n)
{
free(n);
}
...
}
我在 Python 包装器中实现了这段代码以释放 DLL 分配的内存:
import os
import ctypes
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
NUMERIC = ctypes.CDLL(DIR_PATH + "/numeric.dll")
...
NUMERIC.free_memory.argtypes = [ctypes.c_void_p]
NUMERIC.free_memory.restype = None
def void_cast(n):
a = ctypes.cast(n, ctypes.c_char_p)
res = ctypes.c_char_p(a.value)
NUMERIC.free_memory(a)
return res
因此,对于 res = ctypes.c_char_p (a.value)
,我创建了一个不再指向 a
的新变量。这样我用DLL的方式正确删除了a
,但是还是有内存泄露的问题。就好像 Python 垃圾收集器没有正确释放 c_char_p
类型字符串的内存。在之前的实现中我只使用了 Python 和 gmpy2
库,所以所有的数字都被转换为 mpz
或 mpq
。我使用 memory_profiler
包测试了内存消耗。我创建了 40 个投影点类型的对象,在椭圆曲线上定义,我计算了产品 i*P
,i
从 1 到 40。gmpy2
总共使用了大约 70MB。相反,在 C++ 中使用带有 类 的 ctypes 内存消耗上升到 1.5GB。很明显有问题,尤其是当只有处理算术运算的基数 类 发生变化时。如何在没有内存泄漏问题的情况下正确释放内存?
我举了一个计算算术运算的 extern
方法的例子,但我已经检查过问题只在于通过 free_memory
函数正确释放内存并重新分配字符串,以便垃圾收集器Python 将在需要时释放字符串。
extern "C" {
__declspec(dllexport) const char* rat_add(const char * n, const char * m)
{
return (RationalNum(n) + RationalNum(m)).getValue();
}
}
在此先致谢,祝您有愉快的一天。
PS:很明显,在 C++ 中,我正确地实现了析构函数方法来释放 mpz_t
和 mpq_t
创建的对象的 space。
问题出在这一行:
res = ctypes.c_char_p(a.value)
这将创建 a.value
的副本并将 res
设置为指向该副本的 c_char_p
。但是Python并没有对ctypes
指针做内存管理,所以复制会泄露!
如果将上面的行替换为:
,应该可以解决泄漏问题res = bytes(memoryview(a.value))
这也会创建一个副本,但是 res
将是一个真正的 Python 对象。