WeakMap.prototype.clear performance (was: Questioning WeakMap.prototype.clear)

2013-01-22 Thread David Bruant

[Merging a couple of relevant posts]

Le 22/01/2013 15:59, Jason Orendorff a écrit :


Now, the only thing that can differentiate both the native against
this version is performance I think. Allen seems to argue that a
native .clear would have better perf characteristics (related to
GC). I still fail to see why the difference would be significant
(but I need to re-read his recent posts about that).


Definitely re-read them. They made sense to me. If you have questions 
about the implementation of GC through WeakMaps, I'll happily share 
what I know.

That would be:
* https://mail.mozilla.org/pipermail/es-discuss/2013-January/028145.html
* https://mail.mozilla.org/pipermail/es-discuss/2013-January/028387.html



As an implementor, what is your feeling about performance
characteristics of both the native and the class-based version?


Heh! I'm the worst person to ask about this. I'm not comfortable with 
the worst-case GC performance of WeakMaps to begin with. My main 
coping mechanism is not thinking about it!


In our current implementation, creating a new WeakMap and dropping the 
old one is very nearly equivalent in performance to clear(). However 
that's because we don't have a generational GC today. Dead WeakMaps 
are promptly collected. In another year, that will change. If we end 
up with more than two generations, I think it'll lead to exactly the 
problems Allen foresees.

For reference, quote from Allen:
generational collectors can have large latencies between the time the 
last reference to an object is destroyed and the when the GC actually 
notices.  Many GC cycles may occur during that period and if a 
populated but unneeded large WeakMap is one of these zombie object, 
then it can have perf impacts.

Jason:
Maybe even if we just have two generations. (To some extent, 
long-lived ordinary Maps and Arrays also do this in a generational GC; 
but WeakMaps have much, much worse worst-case GC performance.)
Indeed, the long-lived object being the only root of a big graph is a 
problem unrelated to WeakMaps. If that was the only reason to add a 
clear method on weakmaps, an Object.clear should be considered too.
I don't understand the point about the worst-case GC performance. It may 
be related to Allen's point about ephemeron algorithms which I know not 
enough about.
I would be interested in knowing more if that's relevant. I'm not 
entirely sure it's relevant since the difference between .clear and 
dropping a weakmap is about the delta during which the storage is 
considered collectable.


Having said all that, I bet we could hack around the worst-case GC 
performance. It'll be a pain, but GC is like that sometimes.
What you said above about the current GC setup that yields equivalence 
performance to .clear is interesting. In a nutshell, moving to a 
(naive?) generational GC means that you're losing something you had 
before. I feel there is a middleground to be found. What about the 
following:
WeakMaps are allocated in their own area which is manually GC'ed with 
today's algorithm (which is probably implemented for the last 
generation?). This way, you'll know as soon as possible (next GC) if one 
is dead.

Variations:
* WeakMaps are moved to this area after a given threshold (20 keys?)
* WeakMaps are moved to this area if they survives one GC. cycle
I feel that with this dedicated area, you know soon enough (next GC 
which is what you get for .clear too) whether a big weakmap can be 
collected.


I wouldn't consider what I suggested as a hack around worst-case GC 
performance, but rather as WeakMap special-casing. Given WeakMaps 
special properties about memory management, it doesn't sound that crazy 
to special case how they're being used in GC algorithms.
Maybe the ideas I suggested above in 5 minutes are not perfect, but I 
feel special-casing weakmaps is a direction to explore regardless of the 
debate we're having about .clear actually since developers won't 
necessarily always use it and the GC needs to be fast in these cases too.



Just to be sure I understand generational GC: old generations considered 
as roots when doing most GC traversing, right? That's why it may take 
time to realize they're actually dead?


Thanks,

David
___
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss


Re: WeakMap.prototype.clear performance (was: Questioning WeakMap.prototype.clear)

2013-01-22 Thread Jason Orendorff
On Tue, Jan 22, 2013 at 1:55 PM, David Bruant bruan...@gmail.com wrote:

 For reference, quote from Allen:

 generational collectors can have large latencies between the time the last
 reference to an object is destroyed and the when the GC actually notices.
 Many GC cycles may occur during that period and if a populated but unneeded
 large WeakMap is one of these zombie object, then it can have perf impacts.

 Jason:

   Maybe even if we just have two generations. (To some extent, long-lived
 ordinary Maps and Arrays also do this in a generational GC; but WeakMaps
 have much, much worse worst-case GC performance.)

 Indeed, the long-lived object being the only root of a big graph is a
 problem unrelated to WeakMaps. If that was the only reason to add a clear
 method on weakmaps, an Object.clear should be considered too.


Not quite unrelated to WeakMaps, since this issue combines with the O(n^2)
WeakMap GC algorithm to cause the performance problem. But otherwise, yes.

I don't understand the point about the worst-case GC performance. It may be
 related to Allen's point about ephemeron algorithms which I know not enough
 about.
 I would be interested in knowing more if that's relevant. I'm not entirely
 sure it's relevant since the difference between .clear and dropping a
 weakmap is about the delta during which the storage is considered
 collectable.


OK, deep dive follows...

Think of our GC as a simple stop-the-world mark-and-sweep algorithm. (It's
incremental, to reduce pause times, but that doesn't come into this
conversation.)

During marking, when a WeakMap is marked, the keys and values are not
marked. Instead the WeakMap is added to a list of marked (i.e.
already-known-reachable) WeakMaps.

10. Once ordinary marking is done, we do a linear pass through all marked
WeakMaps. For each WeakMap, for each entry, if the key is marked, we mark
the value. If this pass does not find any previously-unmarked objects,
we're done. But if we did, we must then resume ordinary marking in order to
mark everything (transitively) reachable from those values. (Of course this
step may mark additional WeakMaps that were not marked before.) Now goto 10.

The maximum number of times the above loop may execute is the number of
entries in WeakMaps. So it is an O(n^2) algorithm. The worst case occurs
when WeakMap values lead to other WeakMaps or WeakMap keys, which lead to
still more... I hope it's clear how this worst-case behavior can be
triggered when, instead of clearing, an older WeakMap is dropped, to be
collected, but possibly pointing to existing objects from which the newer
WeakMap is reachable. If this happens N times, you could get a chain of N
WeakMaps. Chaining is the problem. Note that in a generational GC, you
would expect the *oldest* WeakMaps in the chain to be treated as roots.

Easiest possible hack: detect this worst-case behavior, and flip a bit so
that the system does only full GCs. It essentially stops acting like a
generational GC. This is not a great hack, though. GC is still O(N^2); but
N is bounded by the number of garbage WeakMaps you can end up with between
GC cycles.

I think the algorithm's asymptotic efficiency could be improved at the cost
of adding a branch in the marking code (or, more likely—since branches in
tight loops are expensive—compiling two versions of all the marking code,
one for pre-WeakMap marking and one for post-WeakMap marking). That is a
bigger implementation investment.


Having said all that, I bet we could hack around the worst-case GC
 performance. It'll be a pain, but GC is like that sometimes.

 What you said above about the current GC setup that yields equivalence
 performance to .clear is interesting. In a nutshell, moving to a (naive?)
 generational GC means that you're losing something you had before. I feel
 there is a middleground to be found.


What you're losing when you switch to a generational GC is precision. The
tradeoff is: you do a lot less GC work, but you collect objects that
survive a generation much less aggressively.


 What about the following:
 WeakMaps are allocated in their own area which is manually GC'ed with
 today's algorithm (which is probably implemented for the last generation?).
 This way, you'll know as soon as possible (next GC) if one is dead.


I don't understand. How do you know if one is dead, short of marking the
entire heap?

Given WeakMaps special properties about memory management, it doesn't sound
 that crazy to special case how they're being used in GC algorithms.
 Maybe the ideas I suggested above in 5 minutes are not perfect, but I feel
 special-casing weakmaps is a direction to explore regardless of the debate
 we're having about .clear actually since developers won't necessarily
 always use it and the GC needs to be fast in these cases too.


As I said earlier, I basically agree with that.

Just to be sure I understand generational GC: old generations considered as
 roots when doing most GC traversing, right? 

Re: WeakMap.prototype.clear performance (was: Questioning WeakMap.prototype.clear)

2013-01-22 Thread Mark S. Miller
On Tue, Jan 22, 2013 at 1:46 PM, Jason Orendorff
jason.orendo...@gmail.com wrote:
 On Tue, Jan 22, 2013 at 1:55 PM, David Bruant bruan...@gmail.com wrote:

 For reference, quote from Allen:

 generational collectors can have large latencies between the time the last
 reference to an object is destroyed and the when the GC actually notices.
 Many GC cycles may occur during that period and if a populated but unneeded
 large WeakMap is one of these zombie object, then it can have perf impacts.

 Jason:

 Maybe even if we just have two generations. (To some extent, long-lived
 ordinary Maps and Arrays also do this in a generational GC; but WeakMaps
 have much, much worse worst-case GC performance.)

 Indeed, the long-lived object being the only root of a big graph is a
 problem unrelated to WeakMaps. If that was the only reason to add a clear
 method on weakmaps, an Object.clear should be considered too.


 Not quite unrelated to WeakMaps, since this issue combines with the O(n^2)
 WeakMap GC algorithm to cause the performance problem. But otherwise, yes.

 I don't understand the point about the worst-case GC performance. It may
 be related to Allen's point about ephemeron algorithms which I know not
 enough about.
 I would be interested in knowing more if that's relevant. I'm not entirely
 sure it's relevant since the difference between .clear and dropping a
 weakmap is about the delta during which the storage is considered
 collectable.


 OK, deep dive follows...

 Think of our GC as a simple stop-the-world mark-and-sweep algorithm. (It's
 incremental, to reduce pause times, but that doesn't come into this
 conversation.)

 During marking, when a WeakMap is marked, the keys and values are not
 marked. Instead the WeakMap is added to a list of marked (i.e.
 already-known-reachable) WeakMaps.

 10. Once ordinary marking is done, we do a linear pass through all marked
 WeakMaps. For each WeakMap, for each entry, if the key is marked, we mark
 the value. If this pass does not find any previously-unmarked objects, we're
 done. But if we did, we must then resume ordinary marking in order to mark
 everything (transitively) reachable from those values. (Of course this step
 may mark additional WeakMaps that were not marked before.) Now goto 10.

 The maximum number of times the above loop may execute is the number of
 entries in WeakMaps. So it is an O(n^2) algorithm. The worst case occurs
 when WeakMap values lead to other WeakMaps or WeakMap keys, which lead to
 still more... I hope it's clear how this worst-case behavior can be
 triggered when, instead of clearing, an older WeakMap is dropped, to be
 collected, but possibly pointing to existing objects from which the newer
 WeakMap is reachable. If this happens N times, you could get a chain of N
 WeakMaps. Chaining is the problem. Note that in a generational GC, you would
 expect the *oldest* WeakMaps in the chain to be treated as roots.

 Easiest possible hack: detect this worst-case behavior, and flip a bit so
 that the system does only full GCs. It essentially stops acting like a
 generational GC. This is not a great hack, though. GC is still O(N^2); but N
 is bounded by the number of garbage WeakMaps you can end up with between GC
 cycles.

 I think the algorithm's asymptotic efficiency could be improved at the cost
 of adding a branch in the marking code (or, more likely—since branches in
 tight loops are expensive—compiling two versions of all the marking code,
 one for pre-WeakMap marking and one for post-WeakMap marking). That is a
 bigger implementation investment.

Yes. With this extra branch, you can use Felix's algorithm at
https://mail.mozilla.org/pipermail/es-discuss/2011-March/013241.html.





 Having said all that, I bet we could hack around the worst-case GC
 performance. It'll be a pain, but GC is like that sometimes.

 What you said above about the current GC setup that yields equivalence
 performance to .clear is interesting. In a nutshell, moving to a (naive?)
 generational GC means that you're losing something you had before. I feel
 there is a middleground to be found.


 What you're losing when you switch to a generational GC is precision. The
 tradeoff is: you do a lot less GC work, but you collect objects that survive
 a generation much less aggressively.


 What about the following:
 WeakMaps are allocated in their own area which is manually GC'ed with
 today's algorithm (which is probably implemented for the last generation?).
 This way, you'll know as soon as possible (next GC) if one is dead.


 I don't understand. How do you know if one is dead, short of marking the
 entire heap?

 Given WeakMaps special properties about memory management, it doesn't
 sound that crazy to special case how they're being used in GC algorithms.
 Maybe the ideas I suggested above in 5 minutes are not perfect, but I feel
 special-casing weakmaps is a direction to explore regardless of the debate
 we're having about .clear actually since developers