From 2e28fb768576010250308d3cf273a700486151e2 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:29:44 -0700 Subject: [PATCH 1/6] Cached co_* attrs --- Doc/c-api/code.rst | 6 +-- Include/cpython/code.h | 9 ++++- Objects/codeobject.c | 81 ++++++++++++++++++++++++++++++++----- Objects/frameobject.c | 7 +++- Tools/scripts/deepfreeze.py | 2 +- 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 9054e7ee3181a5..8c323965810f36 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -93,7 +93,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetVarnames(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_varnames')``. - Returns a new reference to a :c:type:`PyTupleObject` containing the names of + Returns a strong reference to a :c:type:`PyTupleObject` containing the names of the local variables. On error, ``NULL`` is returned and an exception is raised. @@ -102,7 +102,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetCellvars(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_cellvars')``. - Returns a new reference to a :c:type:`PyTupleObject` containing the names of + Returns a strong reference to a :c:type:`PyTupleObject` containing the names of the local variables that are referenced by nested functions. On error, ``NULL`` is returned and an exception is raised. @@ -111,7 +111,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetFreevars(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_freevars')``. - Returns a new reference to a :c:type:`PyTupleObject` containing the names of + Returns a strong reference to a :c:type:`PyTupleObject` containing the names of the free variables. On error, ``NULL`` is returned and an exception is raised. .. versionadded:: 3.11 diff --git a/Include/cpython/code.h b/Include/cpython/code.h index 7ce69022557af0..893ff934d3d879 100644 --- a/Include/cpython/code.h +++ b/Include/cpython/code.h @@ -32,6 +32,13 @@ typedef uint16_t _Py_CODEUNIT; #define _Py_SET_OPCODE(word, opcode) \ do { ((unsigned char *)&(word))[0] = (opcode); } while (0) +typedef struct { + PyObject *_co_code; + PyObject *_co_varnames; + PyObject *_co_cellvars; + PyObject *_co_freevars; +} _PyCoCached; + // To avoid repeating ourselves in deepfreeze.py, all PyCodeObject members are // defined in this macro: #define _PyCode_DEF(SIZE) { \ @@ -90,7 +97,7 @@ typedef uint16_t _Py_CODEUNIT; PyObject *co_qualname; /* unicode (qualname, for reference) */ \ PyObject *co_linetable; /* bytes object that holds location info */ \ PyObject *co_weakreflist; /* to support weakrefs to code objects */ \ - PyObject *_co_code; /* cached co_code object/attribute */ \ + _PyCoCached *_co_cached; /* cached co_* attributes */ \ int _co_firsttraceable; /* index of first traceable instruction */ \ char *_co_linearray; /* array of line offsets */ \ /* Scratch space for extra data relating to the code object. \ diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 7d0d038f489a98..bc1b00c59de85b 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -150,7 +150,22 @@ validate_and_copy_tuple(PyObject *tup) return newtuple; } +static int +init_co_cached(PyCodeObject *self) { + if (self->_co_cached == NULL) { + self->_co_cached = PyMem_New(_PyCoCached, 1); + if (self->_co_cached == NULL) { + PyErr_NoMemory(); + return 1; + } + self->_co_cached->_co_code = NULL; + self->_co_cached->_co_cellvars = NULL; + self->_co_cached->_co_freevars = NULL; + self->_co_cached->_co_varnames = NULL; + } + return 0; +} /****************** * _PyCode_New() ******************/ @@ -336,7 +351,7 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con) /* not set */ co->co_weakreflist = NULL; co->co_extra = NULL; - co->_co_code = NULL; + co->_co_cached = NULL; co->co_warmup = QUICKENING_INITIAL_WARMUP_VALUE; co->_co_linearray_entry_size = 0; @@ -1371,7 +1386,20 @@ _PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra) PyObject * _PyCode_GetVarnames(PyCodeObject *co) { - return get_localsplus_names(co, CO_FAST_LOCAL, co->co_nlocals); + if (init_co_cached(co)) { + return NULL; + } + if (co->_co_cached->_co_varnames != NULL) { + return Py_NewRef(co->_co_cached->_co_varnames); + } + assert(co->_co_cached->_co_varnames == NULL); + PyObject *varnames = get_localsplus_names(co, CO_FAST_LOCAL, co->co_nlocals); + if (varnames == NULL) { + return NULL; + } + co->_co_cached->_co_varnames = Py_NewRef(varnames); + return varnames; + } PyObject * @@ -1383,7 +1411,16 @@ PyCode_GetVarnames(PyCodeObject *code) PyObject * _PyCode_GetCellvars(PyCodeObject *co) { - return get_localsplus_names(co, CO_FAST_CELL, co->co_ncellvars); + if (init_co_cached(co)) { + return NULL; + } + if (co->_co_cached->_co_cellvars != NULL) { + return Py_NewRef(co->_co_cached->_co_cellvars); + } + PyObject *cellvars = get_localsplus_names(co, CO_FAST_CELL, co->co_ncellvars); + assert(co->_co_cached->_co_cellvars == NULL); + co->_co_cached->_co_cellvars = Py_NewRef(cellvars); + return cellvars; } PyObject * @@ -1395,7 +1432,16 @@ PyCode_GetCellvars(PyCodeObject *code) PyObject * _PyCode_GetFreevars(PyCodeObject *co) { - return get_localsplus_names(co, CO_FAST_FREE, co->co_nfreevars); + if (init_co_cached(co)) { + return NULL; + } + if (co->_co_cached->_co_freevars != NULL) { + return Py_NewRef(co->_co_cached->_co_freevars); + } + PyObject *freevars = get_localsplus_names(co, CO_FAST_FREE, co->co_nfreevars); + assert(co->_co_cached->_co_freevars == NULL); + co->_co_cached->_co_freevars = Py_NewRef(freevars); + return freevars; } PyObject * @@ -1421,8 +1467,11 @@ deopt_code(_Py_CODEUNIT *instructions, Py_ssize_t len) PyObject * _PyCode_GetCode(PyCodeObject *co) { - if (co->_co_code != NULL) { - return Py_NewRef(co->_co_code); + if (init_co_cached(co)) { + return NULL; + } + if (co->_co_cached->_co_code != NULL) { + return Py_NewRef(co->_co_cached->_co_code); } PyObject *code = PyBytes_FromStringAndSize((const char *)_PyCode_CODE(co), _PyCode_NBYTES(co)); @@ -1430,8 +1479,8 @@ _PyCode_GetCode(PyCodeObject *co) return NULL; } deopt_code((_Py_CODEUNIT *)PyBytes_AS_STRING(code), Py_SIZE(co)); - assert(co->_co_code == NULL); - co->_co_code = Py_NewRef(code); + assert(co->_co_cached->_co_code == NULL); + co->_co_cached->_co_code = Py_NewRef(code); return code; } @@ -1590,7 +1639,13 @@ code_dealloc(PyCodeObject *co) Py_XDECREF(co->co_qualname); Py_XDECREF(co->co_linetable); Py_XDECREF(co->co_exceptiontable); - Py_XDECREF(co->_co_code); + if (co->_co_cached != NULL) { + Py_XDECREF(co->_co_cached->_co_code); + Py_XDECREF(co->_co_cached->_co_cellvars); + Py_XDECREF(co->_co_cached->_co_freevars); + Py_XDECREF(co->_co_cached->_co_varnames); + PyMem_Free(co->_co_cached); + } if (co->co_weakreflist != NULL) { PyObject_ClearWeakRefs((PyObject*)co); } @@ -2165,7 +2220,13 @@ _PyStaticCode_Dealloc(PyCodeObject *co) deopt_code(_PyCode_CODE(co), Py_SIZE(co)); co->co_warmup = QUICKENING_INITIAL_WARMUP_VALUE; PyMem_Free(co->co_extra); - Py_CLEAR(co->_co_code); + if (co->_co_cached != NULL) { + Py_CLEAR(co->_co_cached->_co_code); + Py_CLEAR(co->_co_cached->_co_cellvars); + Py_CLEAR(co->_co_cached->_co_freevars); + Py_CLEAR(co->_co_cached->_co_varnames); + PyMem_Free(co->_co_cached); + } co->co_extra = NULL; if (co->co_weakreflist != NULL) { PyObject_ClearWeakRefs((PyObject *)co); diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 2e377794312612..9912204782cf29 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -644,9 +644,12 @@ add_load_fast_null_checks(PyCodeObject *co) } i += _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; } - if (changed) { + if (changed && co->_co_cached != NULL) { // invalidate cached co_code object - Py_CLEAR(co->_co_code); + Py_CLEAR(co->_co_cached->_co_code); + Py_CLEAR(co->_co_cached->_co_cellvars); + Py_CLEAR(co->_co_cached->_co_freevars); + Py_CLEAR(co->_co_cached->_co_varnames); } } diff --git a/Tools/scripts/deepfreeze.py b/Tools/scripts/deepfreeze.py index d9c6030fc17c07..28ac2b12f92fa7 100644 --- a/Tools/scripts/deepfreeze.py +++ b/Tools/scripts/deepfreeze.py @@ -276,7 +276,7 @@ def generate_code(self, name: str, code: types.CodeType) -> str: self.write(f".co_name = {co_name},") self.write(f".co_qualname = {co_qualname},") self.write(f".co_linetable = {co_linetable},") - self.write(f"._co_code = NULL,") + self.write(f"._co_cached = NULL,") self.write("._co_linearray = NULL,") self.write(f".co_code_adaptive = {co_code_adaptive},") for i, op in enumerate(code.co_code[::2]): From 4e85e31660f6bee5dd1ca96efb7b43f3c567684f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 20:33:25 +0000 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst diff --git a/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst b/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst new file mode 100644 index 00000000000000..bec44339d851a7 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst @@ -0,0 +1 @@ +Lazily create and cache ``co_*`` attributes for better performance for code getters. From 6cfa7aed2c9d0a4e5f5b415a8735a525f26d1a3b Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:17:18 -0700 Subject: [PATCH 3/6] apply suggestions --- Doc/c-api/code.rst | 6 +++--- Objects/codeobject.c | 44 ++++++++++++++++---------------------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/Doc/c-api/code.rst b/Doc/c-api/code.rst index 8c323965810f36..9054e7ee3181a5 100644 --- a/Doc/c-api/code.rst +++ b/Doc/c-api/code.rst @@ -93,7 +93,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetVarnames(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_varnames')``. - Returns a strong reference to a :c:type:`PyTupleObject` containing the names of + Returns a new reference to a :c:type:`PyTupleObject` containing the names of the local variables. On error, ``NULL`` is returned and an exception is raised. @@ -102,7 +102,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetCellvars(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_cellvars')``. - Returns a strong reference to a :c:type:`PyTupleObject` containing the names of + Returns a new reference to a :c:type:`PyTupleObject` containing the names of the local variables that are referenced by nested functions. On error, ``NULL`` is returned and an exception is raised. @@ -111,7 +111,7 @@ bound into a function. .. c:function:: PyObject* PyCode_GetFreevars(PyCodeObject *co) Equivalent to the Python code ``getattr(co, 'co_freevars')``. - Returns a strong reference to a :c:type:`PyTupleObject` containing the names of + Returns a new reference to a :c:type:`PyTupleObject` containing the names of the free variables. On error, ``NULL`` is returned and an exception is raised. .. versionadded:: 3.11 diff --git a/Objects/codeobject.c b/Objects/codeobject.c index bc1b00c59de85b..5a18f3f40345de 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -156,7 +156,7 @@ init_co_cached(PyCodeObject *self) { self->_co_cached = PyMem_New(_PyCoCached, 1); if (self->_co_cached == NULL) { PyErr_NoMemory(); - return 1; + return -1; } self->_co_cached->_co_code = NULL; self->_co_cached->_co_cellvars = NULL; @@ -1383,23 +1383,29 @@ _PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra) * other PyCodeObject accessor functions ******************/ -PyObject * -_PyCode_GetVarnames(PyCodeObject *co) +static PyObject * +get_cached_locals(PyCodeObject *co, PyObject **cached_field, _PyLocals_Kind kind, int num) { + assert(cached_field != NULL); if (init_co_cached(co)) { return NULL; } - if (co->_co_cached->_co_varnames != NULL) { - return Py_NewRef(co->_co_cached->_co_varnames); + if (*cached_field != NULL) { + return Py_NewRef(*cached_field); } - assert(co->_co_cached->_co_varnames == NULL); - PyObject *varnames = get_localsplus_names(co, CO_FAST_LOCAL, co->co_nlocals); + assert(*cached_field == NULL); + PyObject *varnames = get_localsplus_names(co, kind, num); if (varnames == NULL) { return NULL; } - co->_co_cached->_co_varnames = Py_NewRef(varnames); + *cached_field = Py_NewRef(varnames); return varnames; +} +PyObject * +_PyCode_GetVarnames(PyCodeObject *co) +{ + return get_cached_locals(co, &co->_co_cached->_co_varnames, CO_FAST_LOCAL, co->co_nlocals); } PyObject * @@ -1411,16 +1417,7 @@ PyCode_GetVarnames(PyCodeObject *code) PyObject * _PyCode_GetCellvars(PyCodeObject *co) { - if (init_co_cached(co)) { - return NULL; - } - if (co->_co_cached->_co_cellvars != NULL) { - return Py_NewRef(co->_co_cached->_co_cellvars); - } - PyObject *cellvars = get_localsplus_names(co, CO_FAST_CELL, co->co_ncellvars); - assert(co->_co_cached->_co_cellvars == NULL); - co->_co_cached->_co_cellvars = Py_NewRef(cellvars); - return cellvars; + return get_cached_locals(co, &co->_co_cached->_co_cellvars, CO_FAST_CELL, co->co_ncellvars); } PyObject * @@ -1432,16 +1429,7 @@ PyCode_GetCellvars(PyCodeObject *code) PyObject * _PyCode_GetFreevars(PyCodeObject *co) { - if (init_co_cached(co)) { - return NULL; - } - if (co->_co_cached->_co_freevars != NULL) { - return Py_NewRef(co->_co_cached->_co_freevars); - } - PyObject *freevars = get_localsplus_names(co, CO_FAST_FREE, co->co_nfreevars); - assert(co->_co_cached->_co_freevars == NULL); - co->_co_cached->_co_freevars = Py_NewRef(freevars); - return freevars; + return get_cached_locals(co, &co->_co_cached->_co_freevars, CO_FAST_FREE, co->co_nfreevars); } PyObject * From ce5f0e874de41023cbae89b883f8c938785e10a3 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 7 Oct 2022 13:40:44 -0700 Subject: [PATCH 4/6] fix segfault --- Objects/codeobject.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 5a18f3f40345de..01f38f8b558524 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1387,9 +1387,7 @@ static PyObject * get_cached_locals(PyCodeObject *co, PyObject **cached_field, _PyLocals_Kind kind, int num) { assert(cached_field != NULL); - if (init_co_cached(co)) { - return NULL; - } + assert(co->_co_cached != NULL); if (*cached_field != NULL) { return Py_NewRef(*cached_field); } @@ -1405,6 +1403,9 @@ get_cached_locals(PyCodeObject *co, PyObject **cached_field, _PyLocals_Kind kind PyObject * _PyCode_GetVarnames(PyCodeObject *co) { + if (init_co_cached(co)) { + return NULL; + } return get_cached_locals(co, &co->_co_cached->_co_varnames, CO_FAST_LOCAL, co->co_nlocals); } @@ -1417,6 +1418,9 @@ PyCode_GetVarnames(PyCodeObject *code) PyObject * _PyCode_GetCellvars(PyCodeObject *co) { + if (init_co_cached(co)) { + return NULL; + } return get_cached_locals(co, &co->_co_cached->_co_cellvars, CO_FAST_CELL, co->co_ncellvars); } @@ -1429,6 +1433,9 @@ PyCode_GetCellvars(PyCodeObject *code) PyObject * _PyCode_GetFreevars(PyCodeObject *co) { + if (init_co_cached(co)) { + return NULL; + } return get_cached_locals(co, &co->_co_cached->_co_freevars, CO_FAST_FREE, co->co_nfreevars); } From d998aa14bc8c89dd9fef2d34d942510793846cfe Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 7 Oct 2022 14:38:16 -0700 Subject: [PATCH 5/6] yes --- Objects/codeobject.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 01f38f8b558524..73e33551ef5249 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1384,7 +1384,8 @@ _PyCode_SetExtra(PyObject *code, Py_ssize_t index, void *extra) ******************/ static PyObject * -get_cached_locals(PyCodeObject *co, PyObject **cached_field, _PyLocals_Kind kind, int num) +get_cached_locals(PyCodeObject *co, PyObject **cached_field, + _PyLocals_Kind kind, int num) { assert(cached_field != NULL); assert(co->_co_cached != NULL); From d321cfb923cc9dc0f02c9f646e6963a4e5e756ae Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:00:20 -0700 Subject: [PATCH 6/6] Update 2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst --- .../next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst b/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst index bec44339d851a7..b5c78c16e86d18 100644 --- a/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst +++ b/Misc/NEWS.d/next/C API/2022-10-03-20-33-24.gh-issue-95756.SSmXlG.rst @@ -1 +1 @@ -Lazily create and cache ``co_*`` attributes for better performance for code getters. +Lazily create and cache ``co_`` attributes for better performance for code getters.