# HG changeset patch # User Antoine Pitrou # Date 1229555877 -3600 diff -r 02d00e0ffb16 -r c0b808e1147a Include/objimpl.h --- a/Include/objimpl.h +++ b/Include/objimpl.h @@ -285,6 +285,12 @@ extern PyGC_Head *_PyGC_generation0; g->gc.gc_next = NULL; \ } while (0); +/* True if the object is tracked by the GC. This can be useful to implement + some optimizations. */ +#define _PyObject_GC_IS_TRACKED(o) \ + ((_Py_AS_GC(o))->gc.gc_refs != _PyGC_REFS_UNTRACKED) + + PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t); PyAPI_FUNC(PyObject *) _PyObject_GC_New(PyTypeObject *); PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t); diff -r 02d00e0ffb16 -r c0b808e1147a Include/tupleobject.h --- a/Include/tupleobject.h +++ b/Include/tupleobject.h @@ -45,6 +45,10 @@ PyAPI_FUNC(PyObject *) PyTuple_GetSlice( PyAPI_FUNC(int) _PyTuple_Resize(PyObject **, Py_ssize_t); PyAPI_FUNC(PyObject *) PyTuple_Pack(Py_ssize_t, ...); +/* This function untracks the given tuple if it doesn't contain any objects + tracked by the GC, does nothing otherwise. */ +PyAPI_FUNC(void) _PyTuple_Optimize(PyObject *); + /* Macro, trading safety for speed */ #define PyTuple_GET_ITEM(op, i) (((PyTupleObject *)(op))->ob_item[i]) #define PyTuple_GET_SIZE(op) Py_SIZE(op) diff -r 02d00e0ffb16 -r c0b808e1147a Lib/test/test_gc.py --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -415,6 +415,38 @@ class GCTests(unittest.TestCase): self.assertEqual(gc.get_referents(1, 'a', 4j), []) + def test_is_tracked(self): + # Atomic built-in types are not tracked, user-defined objects and + # mutable containers are. + # NOTE: types with special optimizations (e.g. tuple) have tests + # in their own test files instead. + self.assertFalse(gc.is_tracked(None)) + self.assertFalse(gc.is_tracked(1)) + self.assertFalse(gc.is_tracked(1.0)) + self.assertFalse(gc.is_tracked(1.0 + 5.0j)) + self.assertFalse(gc.is_tracked(True)) + self.assertFalse(gc.is_tracked(False)) + self.assertFalse(gc.is_tracked("a")) + self.assertFalse(gc.is_tracked(u"a")) + self.assertFalse(gc.is_tracked(bytearray("a"))) + self.assertFalse(gc.is_tracked(type)) + self.assertFalse(gc.is_tracked(int)) + self.assertFalse(gc.is_tracked(object)) + self.assertFalse(gc.is_tracked(object())) + + class OldStyle: + pass + class NewStyle(object): + pass + self.assertTrue(gc.is_tracked(gc)) + self.assertTrue(gc.is_tracked(OldStyle)) + self.assertTrue(gc.is_tracked(OldStyle())) + self.assertTrue(gc.is_tracked(NewStyle)) + self.assertTrue(gc.is_tracked(NewStyle())) + self.assertTrue(gc.is_tracked([])) + self.assertTrue(gc.is_tracked({})) + self.assertTrue(gc.is_tracked(set())) + def test_bug1055820b(self): # Corresponds to temp2b.py in the bug report. diff -r 02d00e0ffb16 -r c0b808e1147a Lib/test/test_tuple.py --- a/Lib/test/test_tuple.py +++ b/Lib/test/test_tuple.py @@ -1,5 +1,7 @@ from test import test_support, seq_tests +import gc + class TupleTest(seq_tests.CommonTest): type2test = tuple @@ -82,6 +84,61 @@ class TupleTest(seq_tests.CommonTest): self.assertEqual(repr(a0), "()") self.assertEqual(repr(a2), "(0, 1, 2)") + def _not_tracked(self, t): + self.assertFalse(gc.is_tracked(t), t) + + def _tracked(self, t): + self.assertTrue(gc.is_tracked(t), t) + + def test_track_literals(self): + # Test GC-optimization of tuple literals + x, y, z = 1.5, "a", [] + + self._not_tracked(()) + self._not_tracked((1,)) + self._not_tracked((1, 2)) + self._not_tracked((1, 2, "a")) + self._not_tracked((1, 2, (None, True, False, ()), int)) + self._not_tracked((object(),)) + self._not_tracked(((1, x), y, (2, 3))) + + self._tracked(([],)) + self._tracked(([1],)) + self._tracked(({},)) + self._tracked((set(),)) + self._tracked((x, y, z)) + + def check_track_dynamic(self, tp, always_track): + x, y, z = 1.5, "a", [] + + check = self._tracked if always_track else self._not_tracked + check(tp()) + check(tp([])) + check(tp(set())) + check(tp([1, x, y])) + check(tp(obj for obj in [1, x, y])) + check(tp(set([1, x, y]))) + check(tp(tuple([obj]) for obj in [1, x, y])) + check(tuple(tp([obj]) for obj in [1, x, y])) + + self._tracked(tp([z])) + self._tracked(tp([[x, y]])) + self._tracked(tp([{x: y}])) + self._tracked(tp(obj for obj in [x, y, z])) + self._tracked(tp(tuple([obj]) for obj in [x, y, z])) + self._tracked(tuple(tp([obj]) for obj in [x, y, z])) + + def test_track_dynamic(self): + # Test GC-optimization of dynamically constructed tuples. + self.check_track_dynamic(tuple, False) + + def test_track_subtypes(self): + # Tuple subtypes must always be tracked + class MyTuple(tuple): + pass + self.check_track_dynamic(MyTuple, True) + + def test_main(): test_support.run_unittest(TupleTest) diff -r 02d00e0ffb16 -r c0b808e1147a Modules/gcmodule.c --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1198,6 +1198,26 @@ gc_get_objects(PyObject *self, PyObject return result; } +PyDoc_STRVAR(gc_is_tracked__doc__, +"is_tracked(obj) -> bool\n" +"\n" +"Returns true if the object is tracked by the garbage collector.\n" +"Simple atomic objects will return false.\n" +); + +static PyObject * +gc_is_tracked(PyObject *self, PyObject *obj) +{ + PyObject *result; + + if (PyObject_IS_GC(obj) && IS_TRACKED(obj)) + result = Py_True; + else + result = Py_False; + Py_INCREF(result); + return result; +} + PyDoc_STRVAR(gc__doc__, "This module provides access to the garbage collector for reference cycles.\n" @@ -1212,6 +1232,7 @@ PyDoc_STRVAR(gc__doc__, "set_threshold() -- Set the collection thresholds.\n" "get_threshold() -- Return the current the collection thresholds.\n" "get_objects() -- Return a list of all objects tracked by the collector.\n" +"is_tracked() -- Returns true if a given object is tracked.\n" "get_referrers() -- Return the list of objects that refer to an object.\n" "get_referents() -- Return the list of objects that an object refers to.\n"); @@ -1227,6 +1248,7 @@ static PyMethodDef GcMethods[] = { {"collect", (PyCFunction)gc_collect, METH_VARARGS | METH_KEYWORDS, gc_collect__doc__}, {"get_objects", gc_get_objects,METH_NOARGS, gc_get_objects__doc__}, + {"is_tracked", gc_is_tracked, METH_O, gc_is_tracked__doc__}, {"get_referrers", gc_get_referrers, METH_VARARGS, gc_get_referrers__doc__}, {"get_referents", gc_get_referents, METH_VARARGS, diff -r 02d00e0ffb16 -r c0b808e1147a Objects/abstract.c --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2115,7 +2115,7 @@ PySequence_Tuple(PyObject *v) PyObject *it; /* iter(v) */ Py_ssize_t n; /* guess for result tuple size */ PyObject *result; - Py_ssize_t j; + Py_ssize_t j, nb_tracked; if (v == NULL) return null_error(); @@ -2144,13 +2144,15 @@ PySequence_Tuple(PyObject *v) goto Fail; /* Fill the tuple. */ - for (j = 0; ; ++j) { + for (j = 0, nb_tracked = 0; ; ++j) { PyObject *item = PyIter_Next(it); if (item == NULL) { if (PyErr_Occurred()) goto Fail; break; } + nb_tracked += (PyObject_IS_GC(item) && + _PyObject_GC_IS_TRACKED(item)); if (j >= n) { Py_ssize_t oldn = n; /* The over-allocation strategy can grow a bit faster @@ -2180,6 +2182,8 @@ PySequence_Tuple(PyObject *v) _PyTuple_Resize(&result, j) != 0) goto Fail; + if (j && !nb_tracked) + _PyObject_GC_UNTRACK(result); Py_DECREF(it); return result; diff -r 02d00e0ffb16 -r c0b808e1147a Objects/codeobject.c --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -68,14 +68,14 @@ PyCode_New(int argcount, int nlocals, in intern_strings(varnames); intern_strings(freevars); intern_strings(cellvars); - /* Intern selected string constants */ + /* Intern selected string constants, optimize tuple constants. */ for (i = PyTuple_Size(consts); --i >= 0; ) { PyObject *v = PyTuple_GetItem(consts, i); - if (!PyString_Check(v)) - continue; - if (!all_name_chars((unsigned char *)PyString_AS_STRING(v))) - continue; - PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i)); + if (PyString_Check(v) && + all_name_chars((unsigned char *)PyString_AS_STRING(v))) + PyString_InternInPlace(&PyTuple_GET_ITEM(consts, i)); + else if (PyTuple_CheckExact(v)) + _PyTuple_Optimize(v); } co = PyObject_NEW(PyCodeObject, &PyCode_Type); if (co != NULL) { diff -r 02d00e0ffb16 -r c0b808e1147a Objects/listobject.c --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -2254,6 +2254,7 @@ PyList_AsTuple(PyObject *v) p++; q++; } + _PyTuple_Optimize(w); return w; } diff -r 02d00e0ffb16 -r c0b808e1147a Objects/tupleobject.c --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -78,8 +78,11 @@ PyTuple_New(register Py_ssize_t size) ++numfree[0]; Py_INCREF(op); /* extra INCREF so that this is never freed */ } + else + _PyObject_GC_TRACK(op); +#else + _PyObject_GC_TRACK(op); #endif - _PyObject_GC_TRACK(op); return (PyObject *) op; } @@ -131,10 +134,29 @@ PyTuple_SetItem(register PyObject *op, r return 0; } +void +_PyTuple_Optimize(PyObject *_op) +{ + PyTupleObject *op; + Py_ssize_t i, n; + if (!PyTuple_CheckExact(_op) || !_PyObject_GC_IS_TRACKED(_op)) + return; + op = (PyTupleObject *) _op; + n = Py_SIZE(op); + if (!n) + return; + for (i = 0; i < n; i++) { + PyObject *elt = op->ob_item[i]; + if (PyObject_IS_GC(elt) && _PyObject_GC_IS_TRACKED(elt)) + return; + } + _PyObject_GC_UNTRACK(_op); +} + PyObject * PyTuple_Pack(Py_ssize_t n, ...) { - Py_ssize_t i; + Py_ssize_t i, nb_tracked; PyObject *o; PyObject *result; PyObject **items; @@ -145,11 +167,15 @@ PyTuple_Pack(Py_ssize_t n, ...) if (result == NULL) return NULL; items = ((PyTupleObject *)result)->ob_item; - for (i = 0; i < n; i++) { + for (i = 0, nb_tracked = 0; i < n; i++) { o = va_arg(vargs, PyObject *); Py_INCREF(o); items[i] = o; + nb_tracked += (PyObject_IS_GC(o) + && _PyObject_GC_IS_TRACKED(o)); } + if (n && !nb_tracked) + _PyObject_GC_UNTRACK(result); va_end(vargs); return result; } diff -r 02d00e0ffb16 -r c0b808e1147a Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2013,9 +2013,19 @@ PyEval_EvalFrameEx(PyFrameObject *f, int case BUILD_TUPLE: x = PyTuple_New(oparg); if (x != NULL) { - for (; --oparg >= 0;) { - w = POP(); - PyTuple_SET_ITEM(x, oparg, w); + /* Can't call _PyObject_GC_UNTRACK on empty + tuples. */ + if (oparg) { + Py_ssize_t nb_tracked = 0; + for (; --oparg >= 0;) { + w = POP(); + PyTuple_SET_ITEM(x, oparg, w); + nb_tracked += + (PyObject_IS_GC(w) && + _PyObject_GC_IS_TRACKED(w)); + } + if (!nb_tracked) + _PyObject_GC_UNTRACK(x); } PUSH(x); continue; diff -r 02d00e0ffb16 -r c0b808e1147a Python/modsupport.c --- a/Python/modsupport.c +++ b/Python/modsupport.c @@ -255,7 +255,7 @@ static PyObject * do_mktuple(const char **p_format, va_list *p_va, int endchar, int n, int flags) { PyObject *v; - int i; + int i, nb_tracked; int itemfailed = 0; if (n < 0) return NULL; @@ -263,13 +263,15 @@ do_mktuple(const char **p_format, va_lis return NULL; /* Note that we can't bail immediately on error as this will leak refcounts on any 'N' arguments. */ - for (i = 0; i < n; i++) { + for (i = 0, nb_tracked = 0; i < n; i++) { PyObject *w = do_mkvalue(p_format, p_va, flags); if (w == NULL) { itemfailed = 1; Py_INCREF(Py_None); w = Py_None; } + else nb_tracked += (PyObject_IS_GC(w) + && _PyObject_GC_IS_TRACKED(w)); PyTuple_SET_ITEM(v, i, w); } if (itemfailed) { @@ -283,6 +285,8 @@ do_mktuple(const char **p_format, va_lis "Unmatched paren in format"); return NULL; } + if (n && !nb_tracked) + _PyObject_GC_UNTRACK(v); if (endchar) ++*p_format; return v;