Skip to content

Commit

Permalink
code: make code object use deferred reference counting
Browse files Browse the repository at this point in the history
Code objects are frequently accessed by many threads, so enable deferred
reference counting so that in the future we can skip ref count
operations on code objects during frame evaluation. This requires making
PyCode_Type support GC.
  • Loading branch information
colesbury committed Apr 27, 2023
1 parent 357a207 commit 42d3e11
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 7 deletions.
3 changes: 2 additions & 1 deletion Lib/test/test_capi/test_watchers.py
@@ -1,7 +1,7 @@
import unittest

from contextlib import contextmanager, ExitStack
from test.support import catch_unraisable_exception, import_helper
from test.support import catch_unraisable_exception, import_helper, gc_collect


# Skip this test if the _testcapi module isn't available.
Expand Down Expand Up @@ -347,6 +347,7 @@ def code_watcher(self, which_watcher):

def assert_event_counts(self, exp_created_0, exp_destroyed_0,
exp_created_1, exp_destroyed_1):
gc_collect()
self.assertEqual(
exp_created_0, _testcapi.get_code_watcher_num_created_events(0))
self.assertEqual(
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_gc.py
Expand Up @@ -220,12 +220,12 @@ class B(object):

def test_function(self):
# Tricky: f -> d -> f, code should call d.clear() after the exec to
# break the cycle.
# break the cycle. May collect f.__code__ as well.
d = {}
exec("def f(): pass\n", d)
gc.collect()
del d
self.assertEqual(gc.collect(), 2)
self.assertTrue(2 <= gc.collect() <= 3)

def test_function_tp_clear_leaves_consistent_state(self):
# https://github.com/python/cpython/issues/91636
Expand Down
19 changes: 15 additions & 4 deletions Objects/codeobject.c
Expand Up @@ -6,6 +6,7 @@
#include "pycore_code.h" // _PyCodeConstructor
#include "pycore_frame.h" // FRAME_SPECIALS_SIZE
#include "pycore_interp.h" // PyInterpreterState.co_extra_freefuncs
#include "pycore_object.h" // _PyObject_SET_DEFERRED_REFCOUNT
#include "pycore_opcode.h" // _PyOpcode_Deopt
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_tuple.h" // _PyTuple_ITEMS()
Expand Down Expand Up @@ -550,13 +551,15 @@ _PyCode_New(struct _PyCodeConstructor *con)
}

Py_ssize_t size = PyBytes_GET_SIZE(con->code) / sizeof(_Py_CODEUNIT);
PyCodeObject *co = PyObject_NewVar(PyCodeObject, &PyCode_Type, size);
PyCodeObject *co = PyObject_GC_NewVar(PyCodeObject, &PyCode_Type, size);
if (co == NULL) {
Py_XDECREF(replacement_locations);
PyErr_NoMemory();
return NULL;
}
init_code(co, con);
_PyObject_SET_DEFERRED_REFCOUNT(co);
_PyObject_GC_TRACK(co);
Py_XDECREF(replacement_locations);
return co;
}
Expand Down Expand Up @@ -1668,6 +1671,7 @@ code_dealloc(PyCodeObject *co)
{
notify_code_watchers(PY_CODE_EVENT_DESTROY, co);

_PyObject_GC_UNTRACK(co);
if (co->co_extra != NULL) {
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyCodeObjectExtra *co_extra = co->co_extra;
Expand Down Expand Up @@ -1705,7 +1709,14 @@ code_dealloc(PyCodeObject *co)
if (co->_co_linearray) {
PyMem_Free(co->_co_linearray);
}
PyObject_Free(co);
PyObject_GC_Del(co);
}

static int
code_traverse(PyCodeObject *co, visitproc visit, void *arg)
{
Py_VISIT(co->co_consts);
return 0;
}

static PyObject *
Expand Down Expand Up @@ -2114,9 +2125,9 @@ PyTypeObject PyCode_Type = {
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC, /* tp_flags */
code_new__doc__, /* tp_doc */
0, /* tp_traverse */
(traverseproc)code_traverse, /* tp_traverse */
0, /* tp_clear */
code_richcompare, /* tp_richcompare */
offsetof(PyCodeObject, co_weakreflist), /* tp_weaklistoffset */
Expand Down

0 comments on commit 42d3e11

Please sign in to comment.