python -X showrefcount 报告扩展的负引用计数
python -X showrefcount reporting negative reference counts for extension
当我 运行 cpython 在我写的扩展上带有 -X showrefcount
标志时,当我 return 时它报告负引用计数(例如 [-5538 refs, 13503 blocks]
) None 来自函数(使用 Py-RETURN_NONE
宏)。
已知事实:
- 确切计数在 运行 秒之间变化,但保持在同一数量级内。
- 无论发生什么,它似乎都在缓慢发生;在引用计数变为负值之前,我需要调用扩展函数大约 50,000 次。
- 如果我们用
Py_INCREF(Py_None); return Py_None;
替换Py_RETURN_NONE;
,它
没有任何改变。实际上,我们似乎可以添加任意数量的 Py_INCREF(Py_None)
而根本不会影响引用计数。
- 如果我们用
return Py_None;
替换 Py_RETURN_NONE;
并且 不 增加引用计数,它会出现段错误(如预期的那样)。
- 如果我们将 None return 替换为另一个值,例如
PyLong_FromLong(0);
,问题消失。
这是什么原因?相关问题:为什么在运行空脚本后引用计数不零?
最小示例:
用于 cpython 调试构建的构建命令
$ ./configure --with-pydebug --with-valgrind && make
extension.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *f(void) {
int manifest_bug = 1;
if (manifest_bug) {
Py_RETURN_NONE;
}
else {
return PyLong_FromLong(0);
}
}
static PyMethodDef functions[] = {
{"f", (PyCFunction)f, METH_NOARGS, "" },
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
.m_name = "foo",
.m_size = -1,
.m_methods = functions,
};
PyMODINIT_FUNC PyInit_foo(void) {
return PyModule_Create(&module);
}
setup.py
from setuptools import setup, Extension
name="foo"
def main() -> None:
setup(
name=name,
version="0.0.0",
ext_modules=[ Extension(name, ["extension.c"]) ],
)
if __name__ == "__main__":
main()
test.py
import foo
# With fewer than roughly this many iterations, the reference count
# generally remains positive. With more iterations, it becomes more negative
for i in range(50000):
foo.f()
问题是由于扩展是使用旧版本 python 构建的,运行 使用从最新版本编译的调试版本源的版本。未使用 stable ABI 编译的扩展(并声明这样做)在 python 版本之间不二进制兼容。
[感谢 ead 的评论,因为他提出了直接导致此解决方案的问题。]
当我 运行 cpython 在我写的扩展上带有 -X showrefcount
标志时,当我 return 时它报告负引用计数(例如 [-5538 refs, 13503 blocks]
) None 来自函数(使用 Py-RETURN_NONE
宏)。
已知事实:
- 确切计数在 运行 秒之间变化,但保持在同一数量级内。
- 无论发生什么,它似乎都在缓慢发生;在引用计数变为负值之前,我需要调用扩展函数大约 50,000 次。
- 如果我们用
Py_INCREF(Py_None); return Py_None;
替换Py_RETURN_NONE;
,它 没有任何改变。实际上,我们似乎可以添加任意数量的Py_INCREF(Py_None)
而根本不会影响引用计数。 - 如果我们用
return Py_None;
替换Py_RETURN_NONE;
并且 不 增加引用计数,它会出现段错误(如预期的那样)。 - 如果我们将 None return 替换为另一个值,例如
PyLong_FromLong(0);
,问题消失。
这是什么原因?相关问题:为什么在运行空脚本后引用计数不零?
最小示例:
用于 cpython 调试构建的构建命令
$ ./configure --with-pydebug --with-valgrind && make
extension.c
#define PY_SSIZE_T_CLEAN
#include <Python.h>
static PyObject *f(void) {
int manifest_bug = 1;
if (manifest_bug) {
Py_RETURN_NONE;
}
else {
return PyLong_FromLong(0);
}
}
static PyMethodDef functions[] = {
{"f", (PyCFunction)f, METH_NOARGS, "" },
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef module = {
PyModuleDef_HEAD_INIT,
.m_name = "foo",
.m_size = -1,
.m_methods = functions,
};
PyMODINIT_FUNC PyInit_foo(void) {
return PyModule_Create(&module);
}
setup.py
from setuptools import setup, Extension
name="foo"
def main() -> None:
setup(
name=name,
version="0.0.0",
ext_modules=[ Extension(name, ["extension.c"]) ],
)
if __name__ == "__main__":
main()
test.py
import foo
# With fewer than roughly this many iterations, the reference count
# generally remains positive. With more iterations, it becomes more negative
for i in range(50000):
foo.f()
问题是由于扩展是使用旧版本 python 构建的,运行 使用从最新版本编译的调试版本源的版本。未使用 stable ABI 编译的扩展(并声明这样做)在 python 版本之间不二进制兼容。
[感谢 ead 的评论,因为他提出了直接导致此解决方案的问题。]