#11115: Rewrite cached_method in Cython
---------------------------+------------------------------------------------
Reporter: SimonKing | Owner: jason
Type: enhancement | Status: needs_review
Priority: major | Milestone: sage-4.7
Component: misc | Keywords: category cython cache
Author: Simon King | Upstream: N/A
Reviewer: | Merged:
Work_issues: |
---------------------------+------------------------------------------------
Comment(by SimonKing):
I improved the performance of accessing the cache even further.
It is quite common to have methods that do not take arguments but whose
return value should be cached. Typical example is the set of generators of
a multivariate polynomial ring. Since sage-4.7.alpha5, it is a cached
method.
Obviously, if a method takes no arguments, then caching the single value
is much easier than storing the results in a dictionary for different
arguments. I implemented this special case in a class
`CachedMethodCallerNoArgs`. It is automatically used if `@cached_method`
is a applied to a method without arguments. In particular, one does not
need to do changes in the code.
Here is the performance. Bot `I.groebner_basis` and `I.gens` are cached
methods (you need to take sage-4.7.alpha5 for the example):
{{{
sage: P.<a,b,c,d> = QQ[]
sage: I = P*[a,b]
sage: I.gens()
[a, b]
sage: I.groebner_basis()
[a, b]
sage: type(I.gens)
<type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'>
sage: type(I.groebner_basis)
<type 'sage.misc.cachefunc.CachedMethodCaller'>
sage: timeit('I.gens()',number=10^6)
1000000 loops, best of 3: 170 ns per loop
sage: timeit('I.groebner_basis()',number=10^6)
1000000 loops, best of 3: 250 ns per loop
}}}
That is much faster than with an unpatched sage-4.7.alpha5:
{{{
sage: P.<a,b,c,d> = QQ[]
sage: I = P*[a,b]
sage: I.groebner_basis()
[a, b]
sage: I.gens()
[a, b]
sage: timeit('I.gens()',number=10^6)
1000000 loops, best of 3: 699 ns per loop
sage: timeit('I.groebner_basis()',number=10^6)
1000000 loops, best of 3: 746 ns per loop
}}}
To give you an idea of how fast 170 ns or 250 ns are:
{{{
sage: class MyClass:
....: def __init__(self):
....: self._v = 10
....: self.__v = 10
....: def m0(self):
....: return 10
....: def m1(self):
....: return self._v
....: def m2(self):
....: return self.__v
....:
sage: O = MyClass()
sage: timeit('O.m0()')
625 loops, best of 3: 1.01 µs per loop
sage: timeit('O.m1()')
625 loops, best of 3: 622 ns per loop
sage: timeit('O.m2()')
625 loops, best of 3: 621 ns per loop
}}}
Note that the cache is pickled -- see examples in the doc strings.
There is one further extension. There is a class
`sage.misc.cachefunc.ClearCacheOnPickle`, whose purpose is quite nice: If
you have a class that inherits from `clearCacheOnPickle` then the cached
values will ''not'' be pickled.
That is to say: Let X be an instance of ClearCacheOnPickle and assume that
its pickling is implemented using the `__get_newargs__` and
`__get_state__` mantra. Then, the cache of any cached method of
`loads(dumps(X))` will be empty (but of course the cache of X is
preserved).
The idea existed, but it did not work for me. So, I re-implemented it
essentially from scratch, using the original ideas. Also, I provided doc
tests for `ClearCacheOnPickle`; there had been none before.
On top of sage-4.7.alpha5, I have the patches from #10296, #9944, #9138
and #9976, and then all doc tests pass with the patch from here. But there
should only be one dependency, namely of #9976, which provides
introspection to interactive Cython code.
Depends on #9976
--
Ticket URL: <http://trac.sagemath.org/sage_trac/ticket/11115#comment:13>
Sage <http://www.sagemath.org>
Sage: Creating a Viable Open Source Alternative to Magma, Maple, Mathematica,
and MATLAB
--
You received this message because you are subscribed to the Google Groups
"sage-trac" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sage-trac?hl=en.