[Python-Dev] Speeding up name lookups
Oren Tirosh
oren-py-d@hishome.net
Tue, 20 Nov 2001 12:52:26 -0500
Step 1. Define name type
The type 'name' looks and behaves just like a string. Internally, it
contains a reference to a string, a pointer to a dictionary (nm_dict) and
an integer (nm_index). The field nm_dict points to a dictionary where this
name is used as a key. nm_index is the index into the hash table of the
specific dictentry where this name is a key. The validity of this cached
data is verified before using it.
Step 2. Convert co_names to names
When initializing a code object the co_names tuple is converted from strings
to names. A new name object is always allocated for each item in co_names
even if the string it contains was already interned somewhere else.
Step 3. PyDict_GetItem_ByName
This function should be used instead of PyDict_GetItem where lookups are
guaranteed to be performed with name objects as keys and performance is
critical: LOAD_NAME and LOAD_GLOBAL in eval_frame. It may be inlined.
PyObject *
PyDict_GetItem_ByName(PyObject *op, PyObject *key)
{
dictobject *mp = (dictobject *)op;
nameobject *nm = (nameobject *)key;
dictentry *ep;
if (nm->nm_dict == mp) {
ep = &(op->ma_table[nm->nm_index & op->ma_mask ]);
if (ep->me_key == key)
return ep->me_value;
}
return PyDict_GetItem(op,key);
}
Step 4. modify PyDict_GetItem
The above function takes care of cache hits. When a cache miss occurs the
regular PyDict_GetItem should check whether the key is a name and
initialize nm_dict and nm_index for subsequent lookups.
Step 5. PyDict_SetItem_ByName
Similar to PyDict_GetItem_ByName. A cache hit means that the key was
already in the dictionary and only the associated value is modified.
Creating new keys and other cache misses are handled by PyDict_SetItem.
Should be used in STORE_NAME and STORE_GLOBAL.
Step 6. modify PyDict_SetItem
Initialize cache inside name objects for fast lookups by name.
This appears to be a low-risk modification because it affects only a small
part of the code and the cache validity is always verified.
It has a slight performance impact on the speed of the regular GetItem and
SetItem because they need to check if their key is a name and initialize the
cache inside the name object. This should be more than offset by the
improvement in name lookup speed. This may be corrected by adding a third
lookup method in addition to lookdict and lookdict_string.
It is possible to speed up PyDict_GetItem_ByName even further by caching the
value instead of the hash table index:
PyObject *
PyDict_GetItem_ByName(PyObject *op, PyObject *key)
{
nameobject *nm = (nameobject *)key;
if (nm->nm_dict == op)
return nm->nm_value;
else
return PyDict_GetItem(op,key);
}
The penalty is that GetItem and SetItem will need to carefully invalidate or
update the cached values inside name objects because the fast lookup function
cannot verify validity. This also doesn't handle SetItem so the table index
will need to be stored inside the name object as well.
I'd like to hear what you think about this proposal. I believe it should
address many of the issues that PEP 266 and PEP 267 were trying to solve.
In which cases will the cached values be thrashed by two dictionaries that
use the same name as a key?
Oren Tirosh