CPython 3.6 中函数参数的引用计数
Reference count of function arguments in CPython 3.6
为了查明传递给函数的参数是 "temporary"(仅传递到函数中)还是在外部引用,我使用 Py_REFCNT
。这是在 C 扩展包中完成的,但为了更容易重现,我决定在此处提供基于 IPython magic 的 Cython 实现。
在 CPython 3.5 和 CPython 3.6:[=18 之间,接受多个参数的函数似乎发生了一些变化(对于只接受一个参数的函数,它仍然按预期工作) =]
In [1]: %load_ext cython
In [2]: %%cython
...: cdef extern from "Python.h":
...: Py_ssize_t Py_REFCNT(object o)
...:
...: cpdef func(o, p):
...: return Py_REFCNT(o)
当我 运行 3.5 上的代码时,它给了我预期的结果:
>>> import numpy as np
>>> func(np.ones(3), np.ones(3))
1
但是 3.6 给了我 2
:
>>> import numpy as np
>>> func(np.ones(3), np.ones(3))
2
在评论中我被问及 C 代码,这里是:
static PyObject *
GetRefCount(PyObject *m, PyObject *args) {
if (PyTuple_CheckExact(args) && PyTuple_Size(args) > 0) {
Py_ssize_t reference_count = Py_REFCNT(PyTuple_GET_ITEM(args, 0));
return PyLong_FromSsize_t(reference_count);
}
PyErr_SetString(PyExc_TypeError, "wrong input");
return NULL;
}
以及方法定义:
{"getrefcount", /* ml_name */
(PyCFunction)GetRefCount, /* ml_meth */
METH_VARARGS, /* ml_flags */
"" /* ml_doc */
},
结果相同:
>>> import numpy as np
>>> getrefcount(np.ones(3)) # 3.5
1
>>> getrefcount(np.ones(3)) # 3.6
2
我想知道在 3.6 中引用计数在哪里(以及为什么)增加。我查看了 CPython 源代码/Python 问题跟踪器,但找不到答案。
在 Python 3.5 上,当您的函数执行时,参数恰好从调用者的堆栈中清除。在 Python 3.6 上,参数碰巧仍然在调用者的堆栈上以及函数的参数元组中。
在 Python 3.5 上,您的函数调用经过 here:
else {
PyObject *callargs;
callargs = load_args(pp_stack, na);
if (callargs != NULL) {
READ_TIMESTAMP(*pintr0);
C_TRACE(x, PyCFunction_Call(func,callargs,NULL));
READ_TIMESTAMP(*pintr1);
Py_XDECREF(callargs);
}
else {
x = NULL;
}
}
从堆栈中删除参数以构建参数元组:
static PyObject *
load_args(PyObject ***pp_stack, int na)
{
PyObject *args = PyTuple_New(na);
PyObject *w;
if (args == NULL)
return NULL;
while (--na >= 0) {
w = EXT_POP(*pp_stack);
PyTuple_SET_ITEM(args, na, w);
}
return args;
}
在 3.6 上,您的函数调用经过 here:
if (PyCFunction_Check(func)) {
PyThreadState *tstate = PyThreadState_GET();
PCALL(PCALL_CFUNCTION);
stack = (*pp_stack) - nargs - nkwargs;
C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
}
经过 here
PyObject *
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack,
Py_ssize_t nargs, PyObject *kwnames)
{
...
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
Py_XDECREF(kwdict);
return result;
}
经过 here:
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
{
/* Slow-path: create a temporary tuple */
...
tuple = _PyStack_AsTuple(args, nargs);
...
}
经过 here:
for (i=0; i < nargs; i++) {
PyObject *item = stack[i];
Py_INCREF(item);
PyTuple_SET_ITEM(args, i, item);
}
它将参数留在堆栈上并构建一个包含对参数的新引用的元组。
为了查明传递给函数的参数是 "temporary"(仅传递到函数中)还是在外部引用,我使用 Py_REFCNT
。这是在 C 扩展包中完成的,但为了更容易重现,我决定在此处提供基于 IPython magic 的 Cython 实现。
在 CPython 3.5 和 CPython 3.6:[=18 之间,接受多个参数的函数似乎发生了一些变化(对于只接受一个参数的函数,它仍然按预期工作) =]
In [1]: %load_ext cython
In [2]: %%cython
...: cdef extern from "Python.h":
...: Py_ssize_t Py_REFCNT(object o)
...:
...: cpdef func(o, p):
...: return Py_REFCNT(o)
当我 运行 3.5 上的代码时,它给了我预期的结果:
>>> import numpy as np
>>> func(np.ones(3), np.ones(3))
1
但是 3.6 给了我 2
:
>>> import numpy as np
>>> func(np.ones(3), np.ones(3))
2
在评论中我被问及 C 代码,这里是:
static PyObject *
GetRefCount(PyObject *m, PyObject *args) {
if (PyTuple_CheckExact(args) && PyTuple_Size(args) > 0) {
Py_ssize_t reference_count = Py_REFCNT(PyTuple_GET_ITEM(args, 0));
return PyLong_FromSsize_t(reference_count);
}
PyErr_SetString(PyExc_TypeError, "wrong input");
return NULL;
}
以及方法定义:
{"getrefcount", /* ml_name */
(PyCFunction)GetRefCount, /* ml_meth */
METH_VARARGS, /* ml_flags */
"" /* ml_doc */
},
结果相同:
>>> import numpy as np
>>> getrefcount(np.ones(3)) # 3.5
1
>>> getrefcount(np.ones(3)) # 3.6
2
我想知道在 3.6 中引用计数在哪里(以及为什么)增加。我查看了 CPython 源代码/Python 问题跟踪器,但找不到答案。
在 Python 3.5 上,当您的函数执行时,参数恰好从调用者的堆栈中清除。在 Python 3.6 上,参数碰巧仍然在调用者的堆栈上以及函数的参数元组中。
在 Python 3.5 上,您的函数调用经过 here:
else {
PyObject *callargs;
callargs = load_args(pp_stack, na);
if (callargs != NULL) {
READ_TIMESTAMP(*pintr0);
C_TRACE(x, PyCFunction_Call(func,callargs,NULL));
READ_TIMESTAMP(*pintr1);
Py_XDECREF(callargs);
}
else {
x = NULL;
}
}
从堆栈中删除参数以构建参数元组:
static PyObject *
load_args(PyObject ***pp_stack, int na)
{
PyObject *args = PyTuple_New(na);
PyObject *w;
if (args == NULL)
return NULL;
while (--na >= 0) {
w = EXT_POP(*pp_stack);
PyTuple_SET_ITEM(args, na, w);
}
return args;
}
在 3.6 上,您的函数调用经过 here:
if (PyCFunction_Check(func)) {
PyThreadState *tstate = PyThreadState_GET();
PCALL(PCALL_CFUNCTION);
stack = (*pp_stack) - nargs - nkwargs;
C_TRACE(x, _PyCFunction_FastCallKeywords(func, stack, nargs, kwnames));
}
经过 here
PyObject *
_PyCFunction_FastCallKeywords(PyObject *func, PyObject **stack,
Py_ssize_t nargs, PyObject *kwnames)
{
...
result = _PyCFunction_FastCallDict(func, stack, nargs, kwdict);
Py_XDECREF(kwdict);
return result;
}
经过 here:
case METH_VARARGS:
case METH_VARARGS | METH_KEYWORDS:
{
/* Slow-path: create a temporary tuple */
...
tuple = _PyStack_AsTuple(args, nargs);
...
}
经过 here:
for (i=0; i < nargs; i++) {
PyObject *item = stack[i];
Py_INCREF(item);
PyTuple_SET_ITEM(args, i, item);
}
它将参数留在堆栈上并构建一个包含对参数的新引用的元组。