#12601: @cached_method does not work for special methods
-------------------------------------------------+-------------------------
       Reporter:  saraedum                       |        Owner:  jason
           Type:  enhancement                    |       Status:
       Priority:  minor                          |  needs_review
      Component:  misc                           |    Milestone:  sage-5.12
       Keywords:  cached_method, cache,          |   Resolution:
  operator, special method                       |    Merged in:
        Authors:  Simon King                     |    Reviewers:
Report Upstream:  Completely fixed; Fix          |  Work issues:
  reported upstream                              |       Commit:
         Branch:                                 |     Stopgaps:
   Dependencies:  #15038                         |
-------------------------------------------------+-------------------------
Changes (by {'newvalue': u'Simon King', 'oldvalue': ''}):

 * status:  new => needs_review
 * dependencies:   => #15038
 * author:   => Simon King
 * upstream:  Reported upstream. No feedback yet. => Completely fixed; Fix
     reported upstream


Comment:

 In principle, it would be possible to change `CachedMethod.__get__` to
 deal both with usual and special methods. However, the price to pay would
 be either a slow-down when using cached methods on instances that don't
 allow attribute assignment, or cached special methods would be slow.

 Therefore, I created a new class `CachedSpecialMethod` that overloads the
 `__get__` method. Now, cached_method is a function (not an alias of
 `CachedMethod`) that chooses amongst `CachedMethod` and
 `CachedSpecialMethod` based on the name of the wrapped method.

 I also tried to tweak the case of (usual) cached methods in the case of
 forbidden attribute assignment.

 '''__Timings and examples__'''

 __With the patch:__
 {{{
 sage: class CCache(object):
 ....:     @cached_method
 ....:     def __hash__(self):
 ....:         print "computing hash"
 ....:         return int(5)
 ....:     @cached_method
 ....:     def f(self):
 ....:         print "computing cached method with no args"
 ....:         return 4
 ....:     @cached_method
 ....:     def g(self, x):
 ....:         print "computing cached method with args"
 ....:         return x*2
 ....:
 sage: class CNoncache(object):
 ....:     def __hash__(self):
 ....:         return int(5)
 ....:     def f(self):
 ....:         return 4
 ....:     def g(self, x):
 ....:         return x*2
 ....:
 sage: c = CCache()
 sage: hash(c)
 computing hash
 5
 sage: hash(c)
 5
 sage: c.f()
 computing cached method with no args
 4
 sage: c.f()
 4
 sage: c.g(4)
 computing cached method with args
 8
 sage: c.g(4)
 8
 sage: c.g(int(4))
 8
 sage: %timeit hash(c)
 1000000 loops, best of 3: 241 ns per loop
 sage: %timeit c.f()
 10000000 loops, best of 3: 139 ns per loop
 sage: %timeit c.g(4)
 1000000 loops, best of 3: 694 ns per loop
 sage: n = CNoncache()
 sage: %timeit hash(n)
 1000000 loops, best of 3: 827 ns per loop
 sage: %timeit n.f()
 1000000 loops, best of 3: 389 ns per loop
 sage: %timeit n.g(4)
 1000000 loops, best of 3: 669 ns per loop
 }}}
 Note that the cached hash is ''faster'' than an uncached hash that returns
 a constant number!!

 Without attribute assignment (special methods can't be wrapped in Cython,
 so I can't wrap `__hash__`):
 {{{
 sage: cython("""
 ....: from sage.structure.parent cimport Parent
 ....: from sage.misc.cachefunc import cached_method
 ....: cdef class MyParent(Parent):
 ....:     @cached_method
 ....:     def f(self):
 ....:         return 4
 ....:     @cached_method
 ....:     def g(self,x):
 ....:         return x*2
 ....: """)
 sage: m = MyParent()
 sage: m.f() is m.f()
 True
 sage: m.g(4) is m.g(4)
 True
 sage: %timeit m.f()
 1000000 loops, best of 3: 237 ns per loop
 sage: %timeit m.g(4)
 1000000 loops, best of 3: 831 ns per loop
 }}}

 __Without the patch__

 The classes and instances are defined as above. As we know, caching the
 hash did not work, but of course it did work on usual methods:
 {{{
 sage: hash(c)
 computing hash
 5
 sage: hash(c)
 computing hash
 5
 sage: c.f() is c.f()
 computing cached method with no args
 True
 sage: c.g(int(4)) is c.g(4)
 computing cached method with args
 True
 }}}
 The timing with the possibility to assign attributes is slightly better
 with my patch (but I don't know how stable this is):
 {{{
 sage: %timeit c.f()
 10000000 loops, best of 3: 137 ns per loop
 sage: %timeit c.g(4)
 1000000 loops, best of 3: 702 ns per loop
 }}}
 The timings without attribute assignment did improve with my patch as
 well:
 {{{
 sage: %timeit m.f()
 1000000 loops, best of 3: 334 ns per loop
 sage: %timeit m.g(4)
 1000000 loops, best of 3: 958 ns per loop
 }}}

 '''__Conclusion__'''

 I believe that my patch solves the problem and would be glad about a
 review :)

 I didn't run the full test suite yet.

 It could be that there is an interference with #15038, which I thus add as
 a dependency (it already has positive review).

--
Ticket URL: <http://trac.sagemath.org/ticket/12601#comment:9>
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 unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sage-trac.
For more options, visit https://groups.google.com/groups/opt_out.

Reply via email to