Hi Nicolas,

On 28 Jan., 11:02, "Nicolas M. Thiery" <[email protected]>
wrote:
> There are several reasons to cache the results in x rather than in the
> method foo:
>
> (a) You don't need to calculate the hash of x to retrieve something
>     from the cache; that can be important for large objects (with a
>     costly hash function) with methods taking small arguments as
>     input.

You don't need to calculate the hash of x, in order to access the
values from x.bar.cache. So, that's no problem

Example with #8611:
sage: P.<x,y> = GF(5)[]
sage: I = P*[P.gen()]
sage: I.groebner_basis is I.groebner_basis
True  #NEW: That used to be False!


sage: I.groebner_basis()
[x]
sage: I._cache__groebner_basis
{(('',), ()): [x]}
sage: I._cache__groebner_basis is I.groebner_basis.cache
True

So, the result of Gröbner basis computation is stored in the same
location and using the same keys as before, namely
I._cache__groebner_basis. But in addition, there is a second pointer
I.groebner_basis.cache to it.

> (b) The cache is automatically pickled with the object

That's still the case after #8611 -- simply because the same
dictionary still exists.

> (c) It makes it easy to clear / invalidate the cache of a particular object

No problem after #8611:

sage: J = P*[P.gen()]
sage: J.groebner_basis()
[x]
sage: J._cache__groebner_basis
{(('',), ()): [x]}
sage: I._cache__groebner_basis
{(('',), ()): [x]}
sage: I.groebner_basis.clear_cache()
sage: J._cache__groebner_basis
{(('',), ()): [x]}
sage: I._cache__groebner_basis
{}

Perhaps you misunderstood what it means when I say that the cache is
stored in the cached method: It is NOT a cache that is shared by all
instances of the cached method!

> (d) If the object goes of scope and is wiped from memory, then the
>     same occurs to its cache (a desirable feature; otherwise you would
>     probably be using CachedInParent).

If the object J goes off scope and is wiped from memory, then the same
holds for J.groebner_basis. And when J.groebner_basis vanishes, then
so does J.groebner_basis.cache

> Note that there is also a little technical hurdle:
>
>     sage: class bla:
>     ....:     @cached_method
>     ....:     def f(): pass
>     sage: x = bla()
>     sage: x.f.cache = 1
>     sage: x.f.cache
>     ------------------------------------------------------------
>     Traceback (most recent call last):
>       File "<ipython console>", line 1, in <module>
>     AttributeError: 'CachedMethodCaller' object has no attribute 'cache'
>
> That's because x.f is a new object each time;

That's the point: It USED to be a new object (one reason of slowness).
Now, a cached method inserts itself as a usual attribute, and:

sage: I.groebner_basis is I.groebner_basis
True
sage: I.groebner_basis is J.groebner_basis
False

Of course, if you like to break things, you may do

sage: I.groebner_basis.cache[('magma',),()] = NotImplemented
sage: I.groebner_basis('magma')
NotImplemented

> A further note about (c): in fact, I would really like to have all the
> cache be grouped in a single attribute x._cache, using x._cache["foo"]
> or even x._cache.foo rather than x._cache_foo. Advantages:
>
>  - it's easier to clear/invalidate all the cache at once

Well, it'd be easier to switch the computer off...

But, to be honest, clearing *all* caches at once rather than a single
one is a feature that I did not have in mind.

>  - less pollution of the name space

Why "pollution of the name space"? It is all about attributes.

By the way, it *is* in a single dictionary:
sage: I.groebner_basis.cache is I.__dict__['_cache__groebner_basis']
True

>  - objects that need fast caching could have x._cache (or maybe even
>    x._cache.foo) as a Cython attribute

cached_method does not work *at all* with Cython. Even if you have a
Python class and a Python method:

Put the following in a file cachetest.pyx:

from sage.all import cached_method
class FOO:
    @cached_method
    def bar(self, x, y=3):
        return x*y

Then do
sage: attach cachetest.pyx
Compiling ./cachetest.pyx...
Traceback (most recent call last)
...
AttributeError: 'builtin_function_or_method' object has no attribute
'func_defaults'

Unfortunately, that didn't change with #8611.

But of course, I'd appreciate if there could be a cached_method
implementation for Cython!

> There is one issue that needs to be discussed as well: if we change
> the location where the cache is stored in objects, then any currently
> pickled object will loose its cache. Do we care?

I definitely DID care about that point.

As I pointed out above, the location of the cache did not change, but
the accessibility of the cache was simplified. So, one can do:

WITHOUT #8611
sage: P.<x,y> = GF(5)[]
sage: I = P*[P.gen()]
sage: I.groebner_basis()
[x]
sage: save(I,'tmp')

WITH #8611 (starting a new session)
sage: J = load('tmp.sobj')
sage: J.groebner_basis.cache
{(('',), ()): [x]}

Hence, pickles do not break, the cache (stored with the old version)
is still there.

So, I think that #8611 does address most points that you raise here.

Best regards,
Simon

-- 
To post to this group, send an email to [email protected]
To unsubscribe from this group, send an email to 
[email protected]
For more options, visit this group at http://groups.google.com/group/sage-devel
URL: http://www.sagemath.org

Reply via email to