Python PyLongObject内存泄漏
我的编码方法中存在内存泄漏 静态常量大小\u t\u位每\u位=大小数字*字符位; /** *返回存储Elias gamma编码输入列表的位流的Python int。 */ 静态PyObject* encodePyObject*self,PyObject*obj { 如果!PyList_Checkobj{ PyErr_SetStringPyExc_TypeError,要编码的输入必须是列表; 返回NULL; } const Py_ssize_t list_length=PyList_GET_SIZEobj; size\u t size=\u cell\u divsize\u PyList\u GET\u SIZEobj,\u位/u位; 大小\u t当前\u位=0; 大小\u t当前\u位=0; PyLongObject*bstream=\u PyLong\u Newsize; 如果bstream==NULL{ 返回NULL;/\u PyLong\u新集合错误 } 对于Py\u ssize\u t i=0;iPython PyLongObject内存泄漏,python,c,python-3.x,python-c-api,python-extensions,Python,C,Python 3.x,Python C Api,Python Extensions,我的编码方法中存在内存泄漏 静态常量大小\u t\u位每\u位=大小数字*字符位; /** *返回存储Elias gamma编码输入列表的位流的Python int。 */ 静态PyObject* encodePyObject*self,PyObject*obj { 如果!PyList_Checkobj{ PyErr_SetStringPyExc_TypeError,要编码的输入必须是列表; 返回NULL; } const Py_ssize_t list_length=PyList_GET_SI
- 0{
if-Pyrr_检查信号{
返回NULL;
}
如果当前_位==0{
b流->对象数字[当前数字]=数字0;
如果0左>每位{
左零-=\u位/u位;
++当前_位;
}否则{
当前位+=左零;
如果当前位=每位{
当前_位=0;
++当前_位;
}
零左=0;
}
}否则{
数字掩码=~digit-1>>当前\u位;//启用当前\u位的最高有效位
bstream->ob_digit[当前_digit]&=mask;//将此数字的剩余部分设置为零
数字零写入=\u位每\u位\u-当前\u位;
如果左置零>写入零{
左零-=写入的零;
当前_位=0;
++当前_位;
}否则{
当前位+=左零;
如果当前位=每位{
当前_位=0;
++当前_位;
}
零左=0;
}
}
}
//写数字的二进制表示法
大小\u t位\u左=N+1;
而位_左>0{
if-Pyrr_检查信号{
返回NULL;
}
无符号长掩码=1UL ob_位[当前_位]|=~位-1>>1>>当前_位;
}否则{
b流->ob_位[当前_位]&=~~位-1>>1>>当前_位;
}
++当前_位;
如果当前位=每位{
当前_位=0;
++当前_位;
}
-左位;
掩码>>=1;
}
}
//删除未使用的数字
如果大小-当前数字>1{
大小=当前数字+1;
PyLongObject*new=PyLongObject*PyObject\u Reallocbstream,offsetofPyLongObject,ob\u位+大小*\u位/u位;
ifnew==NULL{
PyObject_Freebstream;
返回PyErr_nomery;
}
b流=新的;
PyObject_InitVarPyVarObject*b流和PyLong_类型、大小;
}
//用零填充剩余部分,覆盖malloc随机性
数字掩码=~数字-1>>当前\u位;
b流->对象数字[当前数字]&=掩码;
返回PyObject*b流;
}
用Python
tracemalloc.start()
encoded = encode(numbers)
sleep(3) # give garbage collection time to run
size, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print("size, peak", size, peak)
print(sys.getsizeof(encoded))
印刷品
size, peak 17053496 17053496
2131708
而不是预期的
size, peak 2131708 ...
2131708
使用我自己的类型而不是PyLongObject,它看起来和我的眼睛一样,完全不会导致内存泄漏和输出
size, peak 2131309 2131309
2131309
这是具有该输出的类型
类型定义结构{
PyObject_VAR_头
uint8_t数据[1];
}比特流;
静态PyTypeObject BitStreamType={
PyVarObject\u HEAD\u INITNULL,0
.tp_name=elias_gamma_code.BitStream,
.tp_dealoc=0,
.tp_free=PyObject_Del,
.tp_basicsize=偏移流、数据、,
.tp_itemsize=sizeofuint8_t,
.tp_flags=Py_TPFLAGS_默认值,
};
因此,使用PyLongObject有一些特别之处,特别是它会导致大量内存使用
我猜这是因为PyObject_Realloc,所以我在这里问:
但显然
如果返回非空指针,则obj已被释放
事实上,使用PyObject_Freeprevious_obj导致被释放的指针未分配malloc错误
Elias gamma编码将非零正二进制整数表示为N个前导零,N是数字-1的位长度,与floorlog2num相同,后跟数字本身
+----+--------+
|数字|γ编码|
+----+--------+
| 1 | 1 |
| 2 | 0 10 |
| 3 | 0 11 |
| 4 | 00 100 |
| 5 | 00 101 |
| ... | |
| 15 | 000 1111 |
| 16 | 0000 1 0000 |
| ... | |
| 31 | 0000 1 1111 |
| 32 | 00000 10 0000 |
列表中的所有数字都可以这样编码和连接。生成的比特流可以毫不含糊地解码到原始列表。在错误路径中,给定
PyLongObject *bstream = _PyLong_New(size);
你必须减少对它的引用。但您没有这样做,例如:
if (PyErr_Occurred()) { // number overflowed or was negative
PyErr_Format(PyExc_ValueError, "Number at list index %zd was negative or too large", i);
return NULL;
} else if (number == 0) { // unencodable
PyErr_Format(PyExc_ValueError, "Zero at list index %zd", i);
return NULL;
}
正确的C习惯用法是转到错误处理标签:
PyLongObject *bstream = NULL;
if (!(bstream = _PyLong_New(size))) {
goto error;
}
if (! some other check) {
goto error;
}
...
return bstream;
error:
// xdecref requires that the pointer is initialized in all
// code paths, either to null or pointing to a valid live PyObject
Py_XDECREF(bstream);
return NULL;
}
另一个问题是,在Python中是否允许realloc一个长对象。。。将来它可能会崩溃
最后这个看起来有点奇怪:
size * _bits_per_digit
PyObject_Realloc将新的大小作为字节,那么为什么要将每个数字的位数乘以-而必须乘以数字类型的宽度大小。在错误路径中,给定
PyLongObject *bstream = _PyLong_New(size);
你必须减少对它的引用。但您没有这样做,例如:
if (PyErr_Occurred()) { // number overflowed or was negative
PyErr_Format(PyExc_ValueError, "Number at list index %zd was negative or too large", i);
return NULL;
} else if (number == 0) { // unencodable
PyErr_Format(PyExc_ValueError, "Zero at list index %zd", i);
return NULL;
}
正确的C习惯用法是转到错误处理标签:
PyLongObject *bstream = NULL;
if (!(bstream = _PyLong_New(size))) {
goto error;
}
if (! some other check) {
goto error;
}
...
return bstream;
error:
// xdecref requires that the pointer is initialized in all
// code paths, either to null or pointing to a valid live PyObject
Py_XDECREF(bstream);
return NULL;
}
另一个问题是,在Python中是否允许realloc一个长对象。。。将来它可能会崩溃
最后这个看起来有点奇怪:
size * _bits_per_digit
PyObject_Realloc将新的大小作为字节,那么为什么您要乘以每个数字的位数-而必须乘以数字类型的宽度大小。这完全是由于您发现的每个数字的大小*_位数!谢谢非常感谢您指出错误的惯用风格,我应该把标签放在哪里?在方法末尾返回之后?通常的方法是:1在函数开始时将所有PyObject*设置为NULL。在函数末尾有错误:xdecref除了结果之外的所有PyObject;返回结果;因此,成功路径和失败路径之间共享相同的清理代码,但失败时结果为空。这完全是由于您发现的每位数大小*\u位\u造成的!谢谢非常感谢您指出错误的惯用风格,我应该把标签放在哪里?在方法末尾返回之后?通常的方法是:1在函数开始时将所有PyObject*设置为NULL。在函数末尾有错误:xdecref除了结果之外的所有PyObject;返回结果;因此,在成功路径和失败路径之间共享相同的清理代码,但失败时结果为NULL。