Speed: bytecode vz C API calls

Bengt Richter bokr at oz.net
Fri Dec 12 02:08:25 EST 2003


On Tue, 9 Dec 2003 23:50:06 -0500, "Francis Avila" <francisgavila at yahoo.com> wrote:

>Stuart Bishop wrote in message ...
>>About the only thing that could be done then is:
>>
>>def memoize(callable):
>>    cache = {}
>>    def proxy(*args, _cache=cache):
>
>Not legal.  *args must always come after default args. There's no way to do
Never say never ;-)
A function has the __get__ method, which is what getattr magic uses to make a bound method.
You can use this put cache in the "self" position of a bound method, like

 >>> def sq(x): return x*x
 ...
 >>> def memoize(func): # callable is a builtin ;-)
 ...     def proxy(cache, *args):
 ...         try: return cache[args]
 ...         except KeyError: return cache.setdefault(args, func(*args))
 ...     return proxy.__get__({}, proxy.__class__)
 ...

 >>> msq = memoize(sq)
 >>> msq
 <bound method function.proxy of {}>
 >>> msq(3)
 9
 >>> msq
 <bound method function.proxy of {(3,): 9}>
 >>> msq(3)
 9
 >>> msq(2)
 4
 >>> msq
 <bound method function.proxy of {(3,): 9, (2,): 4}>

Looking at the code, it might be worth timing, if you have 90%+ cache hits.
I'd be curious to see what you get

 >>> import dis
 >>> dis.dis(msq)
   3           0 SETUP_EXCEPT            12 (to 15)
               3 LOAD_FAST                0 (cache)
               6 LOAD_FAST                1 (args)
               9 BINARY_SUBSCR
              10 RETURN_VALUE
              11 POP_BLOCK
              12 JUMP_FORWARD            41 (to 56)

   4     >>   15 DUP_TOP
              16 LOAD_GLOBAL              2 (KeyError)
              19 COMPARE_OP              10 (exception match)
              22 JUMP_IF_FALSE           29 (to 54)
              25 POP_TOP
              26 POP_TOP
              27 POP_TOP
              28 POP_TOP
              29 LOAD_FAST                0 (cache)
              32 LOAD_ATTR                3 (setdefault)
              35 LOAD_FAST                1 (args)
              38 LOAD_DEREF               0 (func)
              41 LOAD_FAST                1 (args)
              44 CALL_FUNCTION_VAR        0
              47 CALL_FUNCTION            2
              50 RETURN_VALUE
              51 JUMP_FORWARD             2 (to 56)
         >>   54 POP_TOP
              55 END_FINALLY
         >>   56 LOAD_CONST               0 (None)
              59 RETURN_VALUE


>this without changing the calling characteristics of the function, which the
>OP has stated is vital.
Should be ok, but I wonder about non-hashable args. Never expect them?

>
>It is probably possible to custom-build a code object which makes _cache a
>local reference to the outer cache, and play with the bytecode to make the
>LOAD_DEREF into a LOAD_FAST, but it's not worth the effort.  I don't know
>how fast/slow a LOAD_DEREF is compared to a LOAD_FAST, though. In any case,
>it won't help more than a few percent.
The above seems to do it for the cache-hit case ;-)
>
>Jacek, I think this is as fast as pure Python will get you with this
>approach.  If the speed is simply not acceptable, there are some things you
>need to consider, Is this the *real* bottleneck?

Regards,
Bengt Richter




More information about the Python-list mailing list