Re: Speed: bytecode vz C API calls
On Tue, 9 Dec 2003 23:50:06 -0500, "Francis Avila" <francisgavila@ yahoo.com> wrote:
[color=blue]
>Stuart Bishop wrote in message ...[color=green]
>>About the only thing that could be done then is:
>>
>>def memoize(callabl e):
>> cache = {}
>> def proxy(*args, _cache=cache):[/color]
>
>Not legal. *args must always come after default args. There's no way to do[/color]
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
[color=blue][color=green][color=darkred]
>>> def sq(x): return x*x[/color][/color][/color]
...[color=blue][color=green][color=darkred]
>>> def memoize(func): # callable is a builtin ;-)[/color][/color][/color]
... def proxy(cache, *args):
... try: return cache[args]
... except KeyError: return cache.setdefaul t(args, func(*args))
... return proxy.__get__({ }, proxy.__class__ )
...
[color=blue][color=green][color=darkred]
>>> msq = memoize(sq)
>>> msq[/color][/color][/color]
<bound method function.proxy of {}>[color=blue][color=green][color=darkred]
>>> msq(3)[/color][/color][/color]
9[color=blue][color=green][color=darkred]
>>> msq[/color][/color][/color]
<bound method function.proxy of {(3,): 9}>[color=blue][color=green][color=darkred]
>>> msq(3)[/color][/color][/color]
9[color=blue][color=green][color=darkred]
>>> msq(2)[/color][/color][/color]
4[color=blue][color=green][color=darkred]
>>> msq[/color][/color][/color]
<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
[color=blue][color=green][color=darkred]
>>> import dis
>>> dis.dis(msq)[/color][/color][/color]
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_V AR 0
47 CALL_FUNCTION 2
50 RETURN_VALUE
51 JUMP_FORWARD 2 (to 56)[color=blue][color=green]
>> 54 POP_TOP[/color][/color]
55 END_FINALLY[color=blue][color=green]
>> 56 LOAD_CONST 0 (None)[/color][/color]
59 RETURN_VALUE
[color=blue]
>this without changing the calling characteristics of the function, which the
>OP has stated is vital.[/color]
Should be ok, but I wonder about non-hashable args. Never expect them?
[color=blue]
>
>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.[/color]
The above seems to do it for the cache-hit case ;-)[color=blue]
>
>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?[/color]
Regards,
Bengt Richter
On Tue, 9 Dec 2003 23:50:06 -0500, "Francis Avila" <francisgavila@ yahoo.com> wrote:
[color=blue]
>Stuart Bishop wrote in message ...[color=green]
>>About the only thing that could be done then is:
>>
>>def memoize(callabl e):
>> cache = {}
>> def proxy(*args, _cache=cache):[/color]
>
>Not legal. *args must always come after default args. There's no way to do[/color]
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
[color=blue][color=green][color=darkred]
>>> def sq(x): return x*x[/color][/color][/color]
...[color=blue][color=green][color=darkred]
>>> def memoize(func): # callable is a builtin ;-)[/color][/color][/color]
... def proxy(cache, *args):
... try: return cache[args]
... except KeyError: return cache.setdefaul t(args, func(*args))
... return proxy.__get__({ }, proxy.__class__ )
...
[color=blue][color=green][color=darkred]
>>> msq = memoize(sq)
>>> msq[/color][/color][/color]
<bound method function.proxy of {}>[color=blue][color=green][color=darkred]
>>> msq(3)[/color][/color][/color]
9[color=blue][color=green][color=darkred]
>>> msq[/color][/color][/color]
<bound method function.proxy of {(3,): 9}>[color=blue][color=green][color=darkred]
>>> msq(3)[/color][/color][/color]
9[color=blue][color=green][color=darkred]
>>> msq(2)[/color][/color][/color]
4[color=blue][color=green][color=darkred]
>>> msq[/color][/color][/color]
<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
[color=blue][color=green][color=darkred]
>>> import dis
>>> dis.dis(msq)[/color][/color][/color]
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_V AR 0
47 CALL_FUNCTION 2
50 RETURN_VALUE
51 JUMP_FORWARD 2 (to 56)[color=blue][color=green]
>> 54 POP_TOP[/color][/color]
55 END_FINALLY[color=blue][color=green]
>> 56 LOAD_CONST 0 (None)[/color][/color]
59 RETURN_VALUE
[color=blue]
>this without changing the calling characteristics of the function, which the
>OP has stated is vital.[/color]
Should be ok, but I wonder about non-hashable args. Never expect them?
[color=blue]
>
>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.[/color]
The above seems to do it for the cache-hit case ;-)[color=blue]
>
>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?[/color]
Regards,
Bengt Richter
Comment