Index: Include/dictobject.h =================================================================== RCS file: /cvsroot/python/python/dist/src/Include/dictobject.h,v retrieving revision 2.25 diff -c -r2.25 dictobject.h *** Include/dictobject.h 12 Aug 2002 07:21:56 -0000 2.25 --- Include/dictobject.h 20 Aug 2002 19:09:40 -0000 *************** *** 8,14 **** /* Dictionary object type -- mapping from hashable object to object */ /* ! There are three kinds of slots in the table: 1. Unused. me_key == me_value == NULL Does not hold an active (key, value) pair now and never did. Unused can --- 8,14 ---- /* Dictionary object type -- mapping from hashable object to object */ /* ! There are four kinds of slots in the table: 1. Unused. me_key == me_value == NULL Does not hold an active (key, value) pair now and never did. Unused can *************** *** 26,31 **** --- 26,46 ---- (cannot have me_key set to NULL), else the probe sequence in case of collision would have no way to know they were once active. + 4. Negative. me_key == interned_string and me_value == NULL + Inserted into a dictionary when an attempted lookup by name fails. + This happens very often because of the locals/globals/builtins fallback + chain. The negative entry "positively" marks a missing key so a complete + search is not required to know that a key is missing. This entry is + considered occupied and is counted by the dictionary's fill counter. If + the key is subsequently added it will change the negative entry to an + active entry. + + The total number of negative entries in a dictionary is bound by the + number of global and builtin names accessed by the code that uses the + dictionary as a namespace. Negative entries are not added for regular + lookups that fail - this could potentially fill the dictionay with an + unlimited number of unreusable entries. + Note: .popitem() abuses the me_hash field of an Unused or Dummy slot to hold a search finger. The me_hash field of Unused or Dummy slots has no meaning otherwise. *************** *** 84,89 **** --- 99,105 ---- PyAPI_FUNC(PyObject *) PyDict_New(void); PyAPI_FUNC(PyObject *) PyDict_GetItem(PyObject *mp, PyObject *key); + PyAPI_FUNC(PyObject *) _PyDict_GetItem_Interned(PyObject *mp, PyObject *key); PyAPI_FUNC(int) PyDict_SetItem(PyObject *mp, PyObject *key, PyObject *item); PyAPI_FUNC(int) PyDict_DelItem(PyObject *mp, PyObject *key); PyAPI_FUNC(void) PyDict_Clear(PyObject *mp); *************** *** 119,124 **** --- 135,164 ---- PyAPI_FUNC(PyObject *) PyDict_GetItemString(PyObject *dp, char *key); PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, char *key, PyObject *item); PyAPI_FUNC(int) PyDict_DelItemString(PyObject *dp, char *key); + + /* Define this to make PyDict_GETITEM_INTERNED a fast inline macro. + * Disable to use the inline_lookups counter for statistics about the + * hit rate of the inline version */ + #define INLINE_DICT_GETITEM_INTERNED + + #ifdef INLINE_DICT_GETITEM_INTERNED + + #define _QDICT(dict) ((PyDictObject*)dict) + #define _QEP0(dict) (_QDICT(dict)->ma_table) + #define _QMASK(dict) (_QDICT(dict)->ma_mask) + #define _QHASH(istr) (((PyStringObject*)istr)->ob_shash) + + #define PyDict_GETITEM_INTERNED(dict, key) ( \ + (_QEP0(dict)[_QHASH(key) & _QMASK(dict)].me_key == key) ? \ + (_QEP0(dict)[_QHASH(key) & _QMASK(dict)].me_value) : \ + (_PyDict_GetItem_Interned(dict, key)) \ + ) + + #else + + #define PyDict_GETITEM_INTERNED(dict, key) _PyDict_GetItem_Interned(dict, key) + + #endif #ifdef __cplusplus } Index: Objects/dictobject.c =================================================================== RCS file: /cvsroot/python/python/dist/src/Objects/dictobject.c,v retrieving revision 2.129 diff -c -r2.129 dictobject.c *** Objects/dictobject.c 19 Aug 2002 21:43:18 -0000 2.129 --- Objects/dictobject.c 20 Aug 2002 19:09:41 -0000 *************** *** 7,13 **** typedef PyDictObject dictobject; /* Define this out if you don't want conversion statistics on exit. */ ! #undef SHOW_CONVERSION_COUNTS /* See large comment block below. This must be >= 1. */ #define PERTURB_SHIFT 5 --- 7,13 ---- typedef PyDictObject dictobject; /* Define this out if you don't want conversion statistics on exit. */ ! #undef SHOW_DICTIONARY_STATS /* See large comment block below. This must be >= 1. */ #define PERTURB_SHIFT 5 *************** *** 113,125 **** static dictentry * lookdict_string(dictobject *mp, PyObject *key, long hash); ! #ifdef SHOW_CONVERSION_COUNTS static long created = 0L; static long converted = 0L; static void show_counts(void) { fprintf(stderr, "created %ld string dicts\n", created); fprintf(stderr, "converted %ld to normal dicts\n", converted); fprintf(stderr, "%.2f%% conversion rate\n", (100.0*converted)/created); --- 113,134 ---- static dictentry * lookdict_string(dictobject *mp, PyObject *key, long hash); ! #ifdef SHOW_DICTIONARY_STATS static long created = 0L; static long converted = 0L; + static long inline_lookups =0L; + static long fast_lookups =0L; + static long slow_lookups =0L; static void show_counts(void) { + fprintf(stderr, "%ld inline dictionary lookups\n", inline_lookups); + fprintf(stderr, "%ld fast dictionary lookups\n", fast_lookups); + fprintf(stderr, "%ld slow dictionary lookups\n", slow_lookups); + fprintf(stderr, "%.2f%% inline\n", (100.0*inline_lookups)/(slow_lookups+fast_lookups+inline_lookups)); + fprintf(stderr, "%.2f%% slow\n", (100.0*slow_lookups)/(slow_lookups+fast_lookups)); + fprintf(stderr, "created %ld string dicts\n", created); fprintf(stderr, "converted %ld to normal dicts\n", converted); fprintf(stderr, "%.2f%% conversion rate\n", (100.0*converted)/created); *************** *** 154,160 **** dummy = PyString_FromString(""); if (dummy == NULL) return NULL; ! #ifdef SHOW_CONVERSION_COUNTS Py_AtExit(show_counts); #endif } --- 163,169 ---- dummy = PyString_FromString(""); if (dummy == NULL) return NULL; ! #ifdef SHOW_DICTIONARY_STATS Py_AtExit(show_counts); #endif } *************** *** 163,169 **** return NULL; EMPTY_TO_MINSIZE(mp); mp->ma_lookup = lookdict_string; ! #ifdef SHOW_CONVERSION_COUNTS ++created; #endif _PyObject_GC_TRACK(mp); --- 172,178 ---- return NULL; EMPTY_TO_MINSIZE(mp); mp->ma_lookup = lookdict_string; ! #ifdef SHOW_DICTIONARY_STATS ++created; #endif _PyObject_GC_TRACK(mp); *************** *** 316,322 **** strings is to override __eq__, and for speed we don't cater to that here. */ if (!PyString_CheckExact(key)) { ! #ifdef SHOW_CONVERSION_COUNTS ++converted; #endif mp->ma_lookup = lookdict; --- 325,331 ---- strings is to override __eq__, and for speed we don't cater to that here. */ if (!PyString_CheckExact(key)) { ! #ifdef SHOW_DICTIONARY_STATS ++converted; #endif mp->ma_lookup = lookdict; *************** *** 459,467 **** --i; insertdict(mp, ep->me_key, ep->me_hash, ep->me_value); } ! else if (ep->me_key != NULL) { /* dummy entry */ --i; - assert(ep->me_key == dummy); Py_DECREF(ep->me_key); } /* else key == value == NULL: nothing to do */ --- 468,475 ---- --i; insertdict(mp, ep->me_key, ep->me_hash, ep->me_value); } ! else if (ep->me_key != NULL) { /* dummy or negative entry */ --i; Py_DECREF(ep->me_key); } /* else key == value == NULL: nothing to do */ *************** *** 492,497 **** --- 500,592 ---- return (mp->ma_lookup)(mp, key, hash)->me_value; } + /* + First argument must be a valid dictionary, second argument must be an + interned string key. Uses a two-pass search: the first pass is much + faster because it assumes the key is the exact same object used as + the entry key. If this pass fails it falls back to the full search and + updates the dictionary so that the next time the fast search will succeed. + If the entry was indexed by a non-intered key it is replaced by the + interned key. + + Never call this function directly, only through the PyDict_GETITEM_INTERNED + macro. + */ + PyObject * + _PyDict_GetItem_Interned(PyObject *op, PyObject *key) + { + register int i; + register unsigned int perturb; + register dictentry *ep; + dictobject *mp; + long hash; + register unsigned int mask; + dictentry *ep0; + PyObject *oldkey; + + mp = (dictobject *)op; + hash = ((PyStringObject *) key)->ob_shash; /* safe: it's interned */ + mask = mp->ma_mask; + ep0 = mp->ma_table; + i = hash & mask; + ep = &ep0[i]; + + /* Don't repeat work already done by macro: */ + #ifndef INLINE_DICT_GETITEM_INTERNED + if (ep->me_key == key) { + #ifdef SHOW_DICTIONARY_STATS + inline_lookups++; + #endif + return ep->me_value; + } + #endif + + #ifdef SHOW_DICTIONARY_STATS + fast_lookups++; + #endif + + if (ep->me_key != NULL) { + for (perturb = hash; ; perturb >>= PERTURB_SHIFT) { + i = (i << 2) + i + perturb + 1; + ep = &ep0[i & mask]; + if (ep->me_key == key) + return ep->me_value; + if (ep->me_key == NULL) + break; + } + } + + /* Slow lookup fallback: */ + #ifdef SHOW_DICTIONARY_STATS + fast_lookups--; + slow_lookups++; + #endif + + ep = (mp->ma_lookup)(mp, key, hash); + if (ep->me_value) { + /* Replace entry key with interned key */ + oldkey = ep->me_key; + ep->me_key = key; + Py_INCREF(key); + Py_DECREF(oldkey); + return ep->me_value; + } + + /* value is NULL. Make it a valid entry for fast negative lookup */ + if (mp->ma_fill < mp->ma_mask && ep->me_key != key ) { + /* @todo: resize table if necessary */ + assert(ep->me_key == NULL || ep->me_key == dummy); + oldkey = ep->me_key; + ep-> me_key = key; + if (oldkey == NULL) + mp->ma_fill++; + Py_INCREF(key); + Py_XDECREF(oldkey); + } + return ep->me_value; + } + + /* CAUTION: PyDict_SetItem() must guarantee that it won't resize the * dictionary if it is merely replacing the value for an existing key. * This is means that it's safe to loop over a dictionary with *************** *** 1770,1776 **** assert(d->ma_table == NULL && d->ma_fill == 0 && d->ma_used == 0); INIT_NONZERO_DICT_SLOTS(d); d->ma_lookup = lookdict_string; ! #ifdef SHOW_CONVERSION_COUNTS ++created; #endif } --- 1865,1871 ---- assert(d->ma_table == NULL && d->ma_fill == 0 && d->ma_used == 0); INIT_NONZERO_DICT_SLOTS(d); d->ma_lookup = lookdict_string; ! #ifdef SHOW_DICTIONARY_STATS ++created; #endif } Index: Python/ceval.c =================================================================== RCS file: /cvsroot/python/python/dist/src/Python/ceval.c,v retrieving revision 2.331 diff -c -r2.331 ceval.c *** Python/ceval.c 20 Aug 2002 15:43:16 -0000 2.331 --- Python/ceval.c 20 Aug 2002 19:09:42 -0000 *************** *** 1690,1700 **** PyObject_REPR(w)); break; } ! x = PyDict_GetItem(x, w); if (x == NULL) { ! x = PyDict_GetItem(f->f_globals, w); if (x == NULL) { ! x = PyDict_GetItem(f->f_builtins, w); if (x == NULL) { format_exc_check_arg( PyExc_NameError, --- 1690,1700 ---- PyObject_REPR(w)); break; } ! x = PyDict_GETITEM_INTERNED(x, w); if (x == NULL) { ! x = PyDict_GETITEM_INTERNED(f->f_globals, w); if (x == NULL) { ! x = PyDict_GETITEM_INTERNED(f->f_builtins, w); if (x == NULL) { format_exc_check_arg( PyExc_NameError, *************** *** 1709,1744 **** case LOAD_GLOBAL: w = GETITEM(names, oparg); ! if (PyString_CheckExact(w)) { ! /* Inline the PyDict_GetItem() calls. ! WARNING: this is an extreme speed hack. ! Do not try this at home. */ ! long hash = ((PyStringObject *)w)->ob_shash; ! if (hash != -1) { ! PyDictObject *d; ! d = (PyDictObject *)(f->f_globals); ! x = d->ma_lookup(d, w, hash)->me_value; ! if (x != NULL) { ! Py_INCREF(x); ! PUSH(x); ! continue; ! } ! d = (PyDictObject *)(f->f_builtins); ! x = d->ma_lookup(d, w, hash)->me_value; ! if (x != NULL) { ! Py_INCREF(x); ! PUSH(x); ! continue; ! } ! goto load_global_error; ! } ! } ! /* This is the un-inlined version of the code above */ ! x = PyDict_GetItem(f->f_globals, w); if (x == NULL) { ! x = PyDict_GetItem(f->f_builtins, w); if (x == NULL) { - load_global_error: format_exc_check_arg( PyExc_NameError, GLOBAL_NAME_ERROR_MSG, w); --- 1709,1718 ---- case LOAD_GLOBAL: w = GETITEM(names, oparg); ! x = PyDict_GETITEM_INTERNED(f->f_globals, w); if (x == NULL) { ! x = PyDict_GETITEM_INTERNED(f->f_builtins, w); if (x == NULL) { format_exc_check_arg( PyExc_NameError, GLOBAL_NAME_ERROR_MSG, w);