[Python-Dev] Re: Code Generation Idea Was: Bytecode idea

Ludovic Aubry Ludovic.Aubry@logilab.fr
Wed, 26 Feb 2003 19:56:46 +0100


On Wed, Feb 26, 2003 at 01:06:44PM -0500, Terry Reedy wrote:
> 
> "Skip Montanaro" <skip@pobox.com> wrote in message
> news:15964.59840.342254.176499@montanaro.dyndns.org...
> > For code that wants to cleanly cross the boundary between Python
> with no
> > boolean type and Python with a boolean type, you sometimes see
> >
> >     True = 1==1
> >     False = 1==0
> >
> > You get True and False if it didn't exist before and have the added
> benefit
> > that if it does exist, it gets found in globals() or locals()
> instead of in
> > __builtins__ and has the right type.
> 
> This suggests a more radical optimization that might do more speedup
> than all the byte-code fiddling currently proposed: automatically
> localize, at definition time, builtins used within a function.  This
> would be like giving each function a customized implicit set of
> default args: True=True, None=None, len=len, ...etc.
> If a program alters __builtins__, def statement placement would
> determine which version gets used.
> 
Hi,

I have a piece of code that does (almost) exactly that, except at
runtime.
It's purpose is to optimize access to globals used as constants by
replacing the LOAD_GLOBAL opcode by a LOAD_CONST.
It does that by creating a new code object for the function you provide
and using a list of symbols to consider constant.

I called it bind, because it works like the postscript bind operator

the code for the main function is below, you can find the full module
at ftp://ftp.logilab.org/pub/tmp/bind.py

it can provide a 10% speedup although most of the time it is around 3% to 5%

regards,

Ludovic


from dis import HAVE_ARGUMENT
from new import code as make_code, function as make_function
import inspect

LOAD_GLOBAL = 116
LOAD_CONST = 100
EXTENDED_ARG = 143
STORE_GLOBAL = 97

def bind_code(co,globals):
    """Take a code object and a dictionnary and returns a
    new code object where the opcodes LOAD_GLOBAL are replaced
    by LOAD_CONST whenever the global's name appear in the
    dictionnary"""
    consts = list(co.co_consts)
    assigned = {}

    code = co.co_code
    new_code=""
    n = len(code)
    i = 0
    while i < n:
        c = code[i]
        op = ord(c)
        i+=1
        if op >= HAVE_ARGUMENT:
            oparg = ord(code[i]) + ord(code[i+1])*256
            i += 2
        else:
            oparg=None
        if op == LOAD_GLOBAL:
            name = co.co_names[oparg]
            if globals.has_key(name):
                k = assigned.get(name,None)
                if k==None:
                    k=len(consts)
                    assigned[name]=len(consts)
                    consts.append(globals[name])
                op = LOAD_CONST
                oparg=k
        new_code+=chr(op)
        if oparg is not None:
            new_code += chr(oparg & 255)
            new_code += chr( (oparg>>8) & 255 )

    return make_code(co.co_argcount,
                    co.co_nlocals,
                    co.co_stacksize,
                    co.co_flags,
                    new_code,
                    tuple(consts),
                    co.co_names,
                    co.co_varnames,
                    co.co_filename,
                    co.co_name,
                    co.co_firstlineno,
                    co.co_lnotab )


def bind(f,globals):
    """Returns a new function whose code object has been
    bound by bind_code()"""
    newcode = bind_code(f.func_code,globals)
    defaults = f.func_defaults or ()
    return make_function(newcode,f.func_globals,f.func_name,defaults)

-- 
Ludovic Aubry                                 LOGILAB, Paris (France).
http://www.logilab.com   http://www.logilab.fr  http://www.logilab.org