John
> --
> To post to this group, send an email to sage-...@googlegroups.com
> To unsubscribe from this group, send an email to sage-devel+...@googlegroups.com
> For more options, visit this group at http://groups.google.com/group/sage-devel
> URL: http://www.sagemath.org
>
Can you give it a shot with GF(2), so as to hit one of the smallest
possible sage Element?
Cheers,
Nicolas
--
Nicolas M. Thi�ry "Isil" <nth...@users.sf.net>
http://Nicolas.Thiery.name/
Elements of small finite fields are cached, so this test doesn't
really show anything.
sage: GF(2)(1) is GF(2)(-1)
True
> But I guess I
> should try to find out why the element creation became slower for
> GF(2), while it became faster for GF(101), and again slower for
> GF(next_prime(10^6)) (see example on the ticket).
Noise?
I think the increased memory usage is a bigger issue than performance
(it can impact the latter, but in hard-to-measure ways). I'm not as
worried about one field as a precedent, and I'm glad you brought this
up (even if it just sat starred in my inbox for a while). Perhaps for
infrequently accessed fields a level of indirection would be
worthwhile, a single lazily-initialized field (either an actual Python
dictionary, or some cdef object with special fields (+ a dict too?)).
In fact, some objects (e.g. matrices) already have a "cache" dict,
this could be done at a higher level.
Creating a new element means existing class hierarchies will have to
be changed (e.g. RingElement) to take advantage of this, but that may
be what's required.
Also, blindly marking all self-only methods as cached is a huge change
in the time-memory performance tradeoff. In terms of memory, imagine I
did
def all_possible_orders(element_list):
return set(g.order() for g in element_list)
I have now doubled my memory usage if g was small. Some things may be
better to re-compute rather than store (especially if they're biggish
and cheap). If your'e setting up a generic infrastructure, perhaps the
default could be enabled/disabled with a flag (and with statement
context).
- Robert
This is because you were testing with finite fields that create all
their elements ahead of time and re-use them.
sage: M = matrix(InfinitePolynomialRing(QQ), 100, 100, range(10000))
sage: get_memory_usage()
228.75
sage: max(a.max_index() for a in M.list()) # a cached method
-1
sage: get_memory_usage()
265.00390625
> But perhaps you
> can find a test case where this would be a problem?
I can't think of one off the top of my head, but this is a problem in
general (as explained so well by nbruin in on the ticket).
>> In fact, some objects (e.g. matrices) already have a "cache" dict,
>> this could be done at a higher level.
>
> Yep, I guess the purpose of __cached_methods is similar. Originally, I
> only intended to use it for cached methods, but then I also used it to
> make inherited lazy attributes (such as element_class) work on
> extension classes.
>
>> Creating a new element means existing class hierarchies will have to
>> be changed (e.g. RingElement) to take advantage of this, but that may
>> be what's required.
>
> By "new element" you mean a new class such as ElementWithCachedMethod?
> The patches from #11115 do not change the class hierarchy for things
> like RingElement -- they offer ElementWithCachedMethod, but it is
> currently not more than an offer.
>
>> Also, blindly marking all self-only methods as cached is a huge change
>> in the time-memory performance tradeoff.
>
> Yep. Meanwhile I found that it makes more sense to add the
> @cached_method decorator only to carefully chosen small frequently
> used methods, such as modulus() of finite fields, and in some cases
> caching gen() has a positive effect.
+1 to thoughtful use. It's a wonderful decorator. (Actually, I wanted
it at work for something just yesterday, but I was using Java :(.
>> Some things may be
>> better to re-compute rather than store (especially if they're biggish
>> and cheap).
>
> In what sense do you mean "better"?
That depends on how much time you have vs. how much memory you have.
As another example, it'd be a waste to cache parent() or the degree of
a univariate polynomial.
That's because MyClass is implemented in Python, and cached_method in
Cython :).
> Or do you mean "better memory-wise"? That may be true, if the result
> of a method is big and not often used.
Yes.
> However, there frequently are methods that look like
> def bla(self):
> try:
> return self.__bla
> except AttributeError:
> do some lengthy computation
> self.__bla = result
> return result
>
> Those should be replaced by
> @cached_method
> def bla(self):
> do some lengthy computation
> return result
>
> That's both shorter and faster.
I agree completely, I was more referring to marking all methods by default.
>> If your'e setting up a generic infrastructure, perhaps the
>> default could be enabled/disabled with a flag (and with statement
>> context).
>
> I don't understand that. Can you give me an example?
You'd have a "maybe_cache" decorator that would decide to cache based
on a global flag. Then you'd do
with aggressive_caching():
[some calculation that uses maybe_cache decorated methods
[here, all caches are cleared and memory reclaimed]
Probably not worth it, and things will get garbage collected when
you're done with them.
- Robert
Or perhaps you're referring to the total startup memory? We create
lots of Parents at startup, but (relatively) few elements (compared to
typical use).
In any case, I like the current solution best.