"chained getattr/module global lookup" optimization (discussion during trillke-sprint 2007, anto/holger, a bit of samuele and cf earlier on)
random example:
- code:
- import os.path normed = [os.path.normpath(p) for p in somelist]
- bytecode:
- [...]
- LOAD_GLOBAL (os) LOAD_ATTR (path) LOAD_ATTR (normpath) LOAD_FAST (p) CALL_FUNCTION 1
would be turned by pypy-compiler into:
LOAD_CHAINED_GLOBAL (os,path,normpath) LOAD_FAST (p) CALL_FUNCTION 1now for the LOAD_CHAINED_GLOBAL bytecode implementation:
Module dicts have a special implemnetation, providing:
an extra "fastlookup" rpython-dict serving as a cache for LOAD_CHAINED_GLOBAL places within the modules:
keys are e.g. ('os', 'path', 'normpath')
values are tuples of the form: ([obj1, obj2, obj3], [ver1, ver2])
"ver1" refer to the version of the globals of "os" "ver2" refer to the version of the globals of "os.path" "obj3" is the resulting "normpath" function
upon changes to the global dict, "fastlookup.clear()" is called
after the fastlookup entry is filled for a given LOAD_CHAINED_GLOBAL index, the following checks need to be performed in the bytecode implementation:
value = f_globals.fastlookup.get(key, None) if value is None: # fill entry else: # check that our cached lookups are still valid assert isinstance(value, tuple) objects, versions = value i = 0 while i < len(versions): lastversion = versions[i] ver = getver_for_obj(objects[i]) if ver == -1 or ver != lastversion: name = key[i] objects[i] = space.getattr(curobj, name) versions[i] = ver curobj = objects[i] i += 1 return objects[i] def getver_for_obj(obj): if "obj is not Module": return -1 return obj.w_dict.version