diff -r c2ae3b5367c9 Include/dictobject.h --- a/Include/dictobject.h Wed Oct 27 15:25:45 2010 +0200 +++ b/Include/dictobject.h Wed Oct 27 20:59:55 2010 +0200 @@ -54,6 +54,8 @@ typedef struct { PyObject *me_value; } PyDictEntry; +typedef Py_ssize_t _PyDict_version_t; + /* To ensure the lookup algorithm terminates, there must be at least one Unused slot (NULL key) in the table. @@ -83,6 +85,8 @@ struct _dictobject { PyDictEntry *ma_table; PyDictEntry *(*ma_lookup)(PyDictObject *mp, PyObject *key, Py_hash_t hash); PyDictEntry ma_smalltable[PyDict_MINSIZE]; + + _PyDict_version_t ma_version; }; PyAPI_DATA(PyTypeObject) PyDict_Type; diff -r c2ae3b5367c9 Include/eval.h --- a/Include/eval.h Wed Oct 27 15:25:45 2010 +0200 +++ b/Include/eval.h Wed Oct 27 20:59:55 2010 +0200 @@ -17,6 +17,13 @@ PyAPI_FUNC(PyObject *) PyEval_EvalCodeEx PyObject **defs, int defc, PyObject *kwdefs, PyObject *closure); +PyAPI_FUNC(PyObject *) _PyEval_EvalCodeExEx( + PyCodeObject *co, + PyObject *globals, PyObject *locals, + PyObject **args, int argc, PyObject **kwds, int kwdc, + PyObject **defs, int defc, PyObject *kwdefs, PyObject *closure, + _PyGlobalsCache *gcache); + PyAPI_FUNC(PyObject *) _PyEval_CallTracing(PyObject *func, PyObject *args); #ifdef __cplusplus diff -r c2ae3b5367c9 Include/frameobject.h --- a/Include/frameobject.h Wed Oct 27 15:25:45 2010 +0200 +++ b/Include/frameobject.h Wed Oct 27 20:59:55 2010 +0200 @@ -36,6 +36,8 @@ typedef struct _frame { macros in ceval.c for details of their use. */ PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; + _PyGlobalsCache *f_gcache; + PyThreadState *f_tstate; int f_lasti; /* Last instruction if called */ /* Call PyFrame_GetLineNumber() instead of reading this field @@ -58,6 +60,9 @@ PyAPI_DATA(PyTypeObject) PyFrame_Type; PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *, PyObject *, PyObject *); +PyAPI_FUNC(PyFrameObject *) _PyFrame_NewEx(PyThreadState *, PyCodeObject *, + PyObject *, PyObject *, + _PyGlobalsCache *); /* The rest of the interface is specific for frame objects */ @@ -81,6 +86,12 @@ PyAPI_FUNC(int) PyFrame_ClearFreeList(vo /* Return the line of code the frame is currently executing. */ PyAPI_FUNC(int) PyFrame_GetLineNumber(PyFrameObject *); +PyAPI_FUNC(void) _PyGlobalsCache_Init(_PyGlobalsCache *, PyCodeObject *, + PyDictObject *globals); +PyAPI_FUNC(void) _PyGlobalsCache_Invalidate(_PyGlobalsCache *, PyCodeObject *, + PyDictObject *globals); +PyAPI_FUNC(void) _PyGlobalsCache_Destroy(_PyGlobalsCache *); + #ifdef __cplusplus } #endif diff -r c2ae3b5367c9 Include/funcobject.h --- a/Include/funcobject.h Wed Oct 27 15:25:45 2010 +0200 +++ b/Include/funcobject.h Wed Oct 27 20:59:55 2010 +0200 @@ -7,6 +7,12 @@ extern "C" { #endif +typedef struct { + PyObject **items; + _PyDict_version_t globals_version; + _PyDict_version_t builtins_version; +} _PyGlobalsCache; + /* Function objects and code objects should not be confused with each other: * * Function objects are created by the execution of the 'def' statement. @@ -32,6 +38,8 @@ typedef struct { PyObject *func_module; /* The __module__ attribute, can be anything */ PyObject *func_annotations; /* Annotations, a dict or NULL */ + _PyGlobalsCache func_gcache; + /* Invariant: * func_closure contains the bindings for func_code->co_freevars, so * PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code) @@ -73,6 +81,9 @@ PyAPI_FUNC(int) PyFunction_SetAnnotation #define PyFunction_GET_ANNOTATIONS(func) \ (((PyFunctionObject *)func) -> func_annotations) +#define _PyFunction_GET_GLOBALS_CACHE(func) \ + (&(((PyFunctionObject *)func) -> func_gcache)) + /* The classmethod and staticmethod types lives here, too */ PyAPI_DATA(PyTypeObject) PyClassMethod_Type; PyAPI_DATA(PyTypeObject) PyStaticMethod_Type; diff -r c2ae3b5367c9 Lib/test/test_sys.py --- a/Lib/test/test_sys.py Wed Oct 27 15:25:45 2010 +0200 +++ b/Lib/test/test_sys.py Wed Oct 27 20:59:55 2010 +0200 @@ -672,9 +672,9 @@ class SizeofTest(unittest.TestCase): # method-wrapper (descriptor object) check({}.__iter__, size(h + '2P')) # dict - check({}, size(h + '3P2P' + 8*'P2P')) + check({}, size(h + '3P2P' + 8*'P2P' + 'P')) longdict = {1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8} - check(longdict, size(h + '3P2P' + 8*'P2P') + 16*size('P2P')) + check(longdict, size(h + '3P2P' + 8*'P2P' + 'P') + 16*size('P2P')) # dictionary-keyiterator check({}.keys(), size(h + 'P')) # dictionary-valueiterator @@ -713,12 +713,16 @@ class SizeofTest(unittest.TestCase): x = inspect.currentframe() ncells = len(x.f_code.co_cellvars) nfrees = len(x.f_code.co_freevars) - extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\ - ncells + nfrees - 1 - check(x, size(vh + '12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) + extras = ( + # Fast locals + x.f_code.co_stacksize + x.f_code.co_nlocals + + # Closure variables array + ncells + nfrees - 1 + ) + check(x, size(vh + '13P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P')) # function def func(): pass - check(func, size(h + '11P')) + check(func, size(h + '14P')) class c(): @staticmethod def foo(): diff -r c2ae3b5367c9 Objects/dictobject.c --- a/Objects/dictobject.c Wed Oct 27 15:25:45 2010 +0200 +++ b/Objects/dictobject.c Wed Oct 27 20:59:55 2010 +0200 @@ -8,6 +8,7 @@ */ #include "Python.h" +#include "frameobject.h" #include "stringlib/eq.h" @@ -202,6 +203,7 @@ show_track(void) #define INIT_NONZERO_DICT_SLOTS(mp) do { \ (mp)->ma_table = (mp)->ma_smalltable; \ (mp)->ma_mask = PyDict_MINSIZE - 1; \ + (mp)->ma_version = 0; \ } while(0) #define EMPTY_TO_MINSIZE(mp) do { \ @@ -485,6 +487,9 @@ _PyDict_HasOnlyStringKeys(PyObject *dict } \ } while(0) +#define NEW_VERSION(mp) \ + mp->ma_version++; + void _PyDict_MaybeUntrack(PyObject *op) { @@ -670,6 +675,7 @@ dictresize(PyDictObject *mp, Py_ssize_t if (is_oldtable_malloced) PyMem_DEL(oldtable); + NEW_VERSION(mp); return 0; } @@ -809,6 +815,7 @@ PyDict_SetItem(register PyObject *op, Py Py_INCREF(key); if (insertdict(mp, key, hash, value) != 0) return -1; + NEW_VERSION(mp); /* If we added a key, we can safely resize. Otherwise just return! * If fill >= 2/3 size, adjust size. Normally, this doubles or * quaduples the size, but it's also possible for the dict to shrink @@ -863,6 +870,7 @@ PyDict_DelItem(PyObject *op, PyObject *k mp->ma_used--; Py_DECREF(old_value); Py_DECREF(old_key); + NEW_VERSION(mp); return 0; } @@ -933,6 +941,7 @@ PyDict_Clear(PyObject *op) if (table_is_malloced) PyMem_DEL(table); + NEW_VERSION(mp); } /* @@ -1589,6 +1598,7 @@ PyDict_Merge(PyObject *a, PyObject *b, i /* Iterator completed, via error */ return -1; } + NEW_VERSION(mp); return 0; } @@ -1848,6 +1858,7 @@ dict_pop(PyDictObject *mp, PyObject *arg ep->me_value = NULL; mp->ma_used--; Py_DECREF(old_key); + NEW_VERSION(mp); return old_value; } @@ -1906,6 +1917,7 @@ dict_popitem(PyDictObject *mp) mp->ma_used--; assert(mp->ma_table[0].me_value == NULL); mp->ma_table[0].me_hash = i + 1; /* next place to start */ + NEW_VERSION(mp); return res; } diff -r c2ae3b5367c9 Objects/frameobject.c --- a/Objects/frameobject.c Wed Oct 27 15:25:45 2010 +0200 +++ b/Objects/frameobject.c Wed Oct 27 20:59:55 2010 +0200 @@ -14,6 +14,63 @@ #define OFF(x) offsetof(PyFrameObject, x) + +static PyObject *builtin_object; + +int _PyFrame_Init() +{ + builtin_object = PyUnicode_InternFromString("__builtins__"); + if (builtin_object == NULL) + return 0; + return 1; +} + +void +_PyGlobalsCache_Invalidate(_PyGlobalsCache *cache, PyCodeObject *code, + PyDictObject *globals) +{ + PyObject *builtins = NULL; + Py_ssize_t i, nglobals = PyTuple_GET_SIZE(code->co_names); + + for (i = 0; i < nglobals; i++) + cache->items[i] = NULL; + /* Same code as in PyFrame_New() */ + builtins = PyDict_GetItem((PyObject *) globals, builtin_object); + if (builtins) { + if (PyModule_Check(builtins)) { + builtins = PyModule_GetDict(builtins); + assert(!builtins || PyDict_Check(builtins)); + } + else if (!PyDict_Check(builtins)) + builtins = NULL; + } + cache->globals_version = globals->ma_version; + if (builtins) + cache->builtins_version = ((PyDictObject *) builtins)->ma_version; + else + cache->builtins_version = -1; +} + +void +_PyGlobalsCache_Init(_PyGlobalsCache *cache, PyCodeObject *code, + PyDictObject *globals) +{ + Py_ssize_t nglobals = PyTuple_GET_SIZE(code->co_names); + cache->items = (PyObject **) PyObject_MALLOC(nglobals * sizeof(PyObject *)); + _PyGlobalsCache_Invalidate(cache, code, globals); +} + +void +_PyGlobalsCache_Destroy(_PyGlobalsCache *cache) +{ + if (cache->items) { + PyObject_FREE((void *) cache->items); + cache->items = NULL; + cache->globals_version = -1; + cache->builtins_version = -1; + } +} + static PyMemberDef frame_memberlist[] = { {"f_back", T_OBJECT, OFF(f_back), READONLY}, {"f_code", T_OBJECT, OFF(f_code), READONLY}, @@ -581,16 +638,6 @@ PyTypeObject PyFrame_Type = { 0, /* tp_dict */ }; -static PyObject *builtin_object; - -int _PyFrame_Init() -{ - builtin_object = PyUnicode_InternFromString("__builtins__"); - if (builtin_object == NULL) - return 0; - return 1; -} - PyFrameObject * PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyObject *locals) @@ -688,6 +735,7 @@ PyFrame_New(PyThreadState *tstate, PyCod Py_INCREF(code); Py_INCREF(globals); f->f_globals = globals; + f->f_gcache = NULL; /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */ if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) == (CO_NEWLOCALS | CO_OPTIMIZED)) @@ -716,6 +764,16 @@ PyFrame_New(PyThreadState *tstate, PyCod return f; } +PyFrameObject * +_PyFrame_NewEx(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, + PyObject *locals, _PyGlobalsCache *gcache) +{ + PyFrameObject *f = PyFrame_New(tstate, code, globals, locals); + if (f != NULL) + f->f_gcache = gcache; + return f; +} + /* Block management */ void diff -r c2ae3b5367c9 Objects/funcobject.c --- a/Objects/funcobject.c Wed Oct 27 15:25:45 2010 +0200 +++ b/Objects/funcobject.c Wed Oct 27 20:59:55 2010 +0200 @@ -5,6 +5,7 @@ #include "code.h" #include "eval.h" #include "structmember.h" +#include "frameobject.h" PyObject * PyFunction_New(PyObject *code, PyObject *globals) @@ -39,6 +40,9 @@ PyFunction_New(PyObject *code, PyObject op->func_dict = NULL; op->func_module = NULL; op->func_annotations = NULL; + assert(PyDict_Check(globals)); + _PyGlobalsCache_Init(&op->func_gcache, (PyCodeObject *) code, + (PyDictObject *) globals); /* __module__: If module name is in globals, use it. Otherwise, use None. @@ -305,6 +309,9 @@ func_set_code(PyFunctionObject *op, PyOb Py_INCREF(value); op->func_code = value; Py_DECREF(tmp); + _PyGlobalsCache_Destroy(&op->func_gcache); + _PyGlobalsCache_Init(&op->func_gcache, (PyCodeObject *)op->func_code, + (PyDictObject *) op->func_globals); return 0; } @@ -552,6 +559,7 @@ func_dealloc(PyFunctionObject *op) _PyObject_GC_UNTRACK(op); if (op->func_weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *) op); + _PyGlobalsCache_Destroy(&op->func_gcache); Py_DECREF(op->func_code); Py_DECREF(op->func_globals); Py_XDECREF(op->func_module); @@ -627,13 +635,14 @@ function_call(PyObject *func, PyObject * nk = 0; } - result = PyEval_EvalCodeEx( + result = _PyEval_EvalCodeExEx( (PyCodeObject *)PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), (PyObject *)NULL, &PyTuple_GET_ITEM(arg, 0), PyTuple_GET_SIZE(arg), k, nk, d, nd, PyFunction_GET_KW_DEFAULTS(func), - PyFunction_GET_CLOSURE(func)); + PyFunction_GET_CLOSURE(func), + _PyFunction_GET_GLOBALS_CACHE(func)); Py_XDECREF(kwtuple); diff -r c2ae3b5367c9 Python/ceval.c --- a/Python/ceval.c Wed Oct 27 15:25:45 2010 +0200 +++ b/Python/ceval.c Wed Oct 27 20:59:55 2010 +0200 @@ -794,7 +794,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int register PyObject *w; register PyObject *u; register PyObject *t; - register PyObject **fastlocals, **freevars; + register PyObject **fastlocals, **freevars, **gcache_items; + register PyObject *builtins, *globals; + register _PyGlobalsCache *gcache; PyObject *retval = NULL; /* Return value */ PyThreadState *tstate = PyThreadState_GET(); PyCodeObject *co; @@ -1189,6 +1191,15 @@ PyEval_EvalFrameEx(PyFrameObject *f, int consts = co->co_consts; fastlocals = f->f_localsplus; freevars = f->f_localsplus + co->co_nlocals; + globals = f->f_globals; + builtins = f->f_builtins; + assert(PyDict_Check(globals)); + assert(PyDict_Check(builtins)); + gcache = f->f_gcache; + if (gcache) + gcache_items = gcache->items; + else + gcache_items = NULL; first_instr = (unsigned char*) PyBytes_AS_STRING(co->co_code); /* An explanation is in order for the next line. @@ -2047,14 +2058,14 @@ PyEval_EvalFrameEx(PyFrameObject *f, int TARGET(STORE_GLOBAL) w = GETITEM(names, oparg); v = POP(); - err = PyDict_SetItem(f->f_globals, w, v); + err = PyDict_SetItem(globals, w, v); Py_DECREF(v); if (err == 0) DISPATCH(); break; TARGET(DELETE_GLOBAL) w = GETITEM(names, oparg); - if ((err = PyDict_DelItem(f->f_globals, w)) != 0) + if ((err = PyDict_DelItem(globals, w)) != 0) format_exc_check_arg( PyExc_NameError, GLOBAL_NAME_ERROR_MSG, w); break; @@ -2081,9 +2092,9 @@ PyEval_EvalFrameEx(PyFrameObject *f, int } } if (x == NULL) { - x = PyDict_GetItem(f->f_globals, w); + x = PyDict_GetItem(globals, w); if (x == NULL) { - x = PyDict_GetItem(f->f_builtins, w); + x = PyDict_GetItem(builtins, w); if (x == NULL) { format_exc_check_arg( PyExc_NameError, @@ -2097,54 +2108,32 @@ PyEval_EvalFrameEx(PyFrameObject *f, int DISPATCH(); TARGET(LOAD_GLOBAL) - w = GETITEM(names, oparg); - if (PyUnicode_CheckExact(w)) { - /* Inline the PyDict_GetItem() calls. - WARNING: this is an extreme speed hack. - Do not try this at home. */ - Py_hash_t hash = ((PyUnicodeObject *)w)->hash; - if (hash != -1) { - PyDictObject *d; - PyDictEntry *e; - d = (PyDictObject *)(f->f_globals); - e = d->ma_lookup(d, w, hash); - if (e == NULL) { - x = NULL; - break; - } - x = e->me_value; + if (gcache_items != NULL) { + if (gcache->globals_version != ((PyDictObject *) globals)->ma_version || + gcache->builtins_version != ((PyDictObject *) builtins)->ma_version) + _PyGlobalsCache_Invalidate(gcache, co, (PyDictObject *) globals); + else { + x = gcache_items[oparg]; if (x != NULL) { Py_INCREF(x); PUSH(x); - DISPATCH(); + FAST_DISPATCH(); } - d = (PyDictObject *)(f->f_builtins); - e = d->ma_lookup(d, w, hash); - if (e == NULL) { - x = NULL; - break; - } - x = e->me_value; - if (x != NULL) { - Py_INCREF(x); - PUSH(x); - DISPATCH(); - } - goto load_global_error; } } - /* This is the un-inlined version of the code above */ - x = PyDict_GetItem(f->f_globals, w); + w = GETITEM(names, oparg); + x = PyDict_GetItem(globals, w); if (x == NULL) { - x = PyDict_GetItem(f->f_builtins, w); + x = PyDict_GetItem(builtins, w); if (x == NULL) { - load_global_error: format_exc_check_arg( PyExc_NameError, GLOBAL_NAME_ERROR_MSG, w); break; } } + if (gcache_items != NULL) + gcache_items[oparg] = x; Py_INCREF(x); PUSH(x); DISPATCH(); @@ -3064,6 +3053,17 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyOb PyObject **args, int argcount, PyObject **kws, int kwcount, PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure) { + return _PyEval_EvalCodeExEx(co, globals, locals, args, argcount, + kws, kwcount, defs, defcount, kwdefs, + closure, NULL); +} + +PyObject * +_PyEval_EvalCodeExEx(PyCodeObject *co, PyObject *globals, PyObject *locals, + PyObject **args, int argcount, PyObject **kws, int kwcount, + PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure, + _PyGlobalsCache *gcache) +{ register PyFrameObject *f; register PyObject *retval = NULL; register PyObject **fastlocals, **freevars; @@ -3079,7 +3079,7 @@ PyEval_EvalCodeEx(PyCodeObject *co, PyOb assert(tstate != NULL); assert(globals != NULL); - f = PyFrame_New(tstate, co, globals, locals); + f = _PyFrame_NewEx(tstate, co, globals, locals, gcache); if (f == NULL) return NULL; @@ -3948,7 +3948,8 @@ fast_function(PyObject *func, PyObject * take builtins without sanity checking them. */ assert(tstate != NULL); - f = PyFrame_New(tstate, co, globals, NULL); + f = _PyFrame_NewEx(tstate, co, globals, NULL, + _PyFunction_GET_GLOBALS_CACHE(func)); if (f == NULL) return NULL;