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