Discussion:
Proposal: Abstract References
Kevin Smith
2014-10-21 19:53:40 UTC
Permalink
This proposal is intended to supersede both relationships and "function
bind".

https://github.com/zenparsing/es-abstract-refs

Enjoy!
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141021/a329b780/attachment.html>
Domenic Denicola
2014-10-21 20:12:16 UTC
Permalink
This is really cool, Kevin. Thanks for writing it up in more detail. I hope it gets a lot of attention.

My initial worries are largely around the ergonomics---both for authors and implementers---if this is our solution for private state. In particular, I don't think having to create a new WeakMap for every private "member" is very author-friendly. And in general, implementers have voiced (weak) objections to using weak maps for private state in saying that they're not designed to work efficiently in that manner. (And that was with the pattern of one WeakMap per class, not multiple!)

That said, this is really elegant and if nothing else it seems like good underpinnings for some slightly-higher-level private state abstraction. And of course you know I <3 the "bind operator".

Hope to hear other TC39ers' thoughts!
-Domenic
Kevin Smith
2014-10-21 20:30:27 UTC
Permalink
Post by Domenic Denicola
That said, this is really elegant and if nothing else it seems like good
underpinnings for some slightly-higher-level private state abstraction.
Right - the idea is that sugar could be introduced over this for declaring
private fields. Also, this proposal doesn't depend upon WeakMaps for
private fields, per se. We could imagine some new, opaque thing that uses
the same "referenceGet/Set/Delete" mechanism.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141021/6f976742/attachment.html>
Mark S. Miller
2014-10-21 20:33:05 UTC
Permalink
Let's not invent a new mechanism when the existing WeakMaps already have
exactly the right core semantics, and (but for a temporary implementation
dead end) already admit of an efficient implementation.
Post by Kevin Smith
Post by Domenic Denicola
That said, this is really elegant and if nothing else it seems like good
underpinnings for some slightly-higher-level private state abstraction.
Right - the idea is that sugar could be introduced over this for declaring
private fields. Also, this proposal doesn't depend upon WeakMaps for
private fields, per se. We could imagine some new, opaque thing that uses
the same "referenceGet/Set/Delete" mechanism.
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141021/e0d2f2f0/attachment.html>
Kevin Smith
2014-10-21 20:34:38 UTC
Permalink
Post by Mark S. Miller
Let's not invent a new mechanism when the existing WeakMaps already have
exactly the right core semantics, and (but for a temporary implementation
dead end) already admit of an efficient implementation.
yep - sounds good to me : )
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141021/09f01cca/attachment-0001.html>
Domenic Denicola
2014-10-21 20:37:51 UTC
Permalink
From: Kevin Smith [mailto:zenparsing at gmail.com]
Also, this proposal doesn't depend upon WeakMaps for private fields, per se.? We could imagine some new, opaque thing that uses the same "referenceGet/Set/Delete" mechanism.
That's a good point. Just to prove it, here's a hypothetical PrivateSymbol version:

```js
PrivateSymbol.prototype[Symbol.referenceGet] = function (obj) {
return obj[this];
};

PrivateSymbol.prototype[Symbol.referenceSet] = function (obj, value) {
obj[this] = value;
};

PrivateSymbol.prototype[Symbol.referenceDelete] = function (obj) {
delete obj[this];
};

const _x = new PrivateSymbol("x");

// ... inside a class
this::_x = x;
```

Note that this is pretty much exactly the same as a WeakMap in terms of author-facing ergonomics, but for implementers it gives them a separate PrivateSymbol type that they can optimize with a use case focused toward private state and object property access, instead of as an ephemeron table.
Domenic Denicola
2014-10-21 20:39:25 UTC
Permalink
Sorry Mark; didn't see your replies before pressing "Send." If we can 'fix' weak map implementations in this way (and nobody has said anything otherwise to my knowledge), then indeed we're good.
Mark S. Miller
2014-10-21 20:31:07 UTC
Permalink
On Tue, Oct 21, 2014 at 1:12 PM, Domenic Denicola <
Post by Domenic Denicola
This is really cool, Kevin. Thanks for writing it up in more detail. I
hope it gets a lot of attention.
First, I agree this is cool. I am very positive on this direction. Kudos!
Post by Domenic Denicola
My initial worries are largely around the ergonomics---both for authors
and implementers---if this is our solution for private state. In
particular, I don't think having to create a new WeakMap for every private
"member" is very author-friendly. And in general, implementers have voiced
(weak) objections to using weak maps for private state
We've realized a long time ago that WeakMaps should be implemented using
the so-called "transposed" representation. All the realistic use cases
either strongly prefer the transposed representation, or are neutral
between the two representations. With the transposed representation, the
storage logic is essentially the same as in the once-imagined private
symbol proposals.
Post by Domenic Denicola
in saying that they're not designed to work efficiently in that manner.
Actually, they are. The problem is that initial implementations date from
before we appreciated the need for the transposed representation. The best
thing TC39 can do for the future of WeakMaps is to proceed assuming the
transposed representation.
Post by Domenic Denicola
(And that was with the pattern of one WeakMap per class, not multiple!)
Actually, that leads to better efficiency, as each WeakMap identity plays
the role that private symbols would have played -- naming an individual
private field. Using only one WeakMap identity per class would require some
other means of naming the individual fields within that class' private
state, necessitating a costly additional level of indirection.

The relationship proposal this is based on indeed uses one WeakMap per
private field.
Post by Domenic Denicola
That said, this is really elegant and if nothing else it seems like good
underpinnings for some slightly-higher-level private state abstraction. And
of course you know I <3 the "bind operator".
Hope to hear other TC39ers' thoughts!
-Domenic
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141021/6b22200f/attachment.html>
Andreas Rossberg
2014-10-22 06:59:13 UTC
Permalink
Post by Mark S. Miller
in saying that [weak maps] are not designed to work efficiently in that manner.
Actually, they are. The problem is that initial implementations date from
before we appreciated the need for the transposed representation. The best
thing TC39 can do for the future of WeakMaps is to proceed assuming the
transposed representation.
While I sympathise, let me clarify that this still remains a
conjecture. AFAIK, nobody has proved it empirically in a JS
implementation yet, we don't know in detail how complex such an
implementation would be, and what side effects it might have on
general performance (e.g., via added polymorphism). It's most likely
not as easy or as clear a win as you may think it is.

/Andreas
Andrea Giammarchi
2014-10-22 12:18:13 UTC
Permalink
FWIW the Multiple WeakMaps approach seems to be 2X ( Chrome ) up to 3X ( FF
Nightly ) faster than single WeakMap approach but I don't know how much
WeakMap instances cost in terms of GC operations so this bench might be
very pointless ( and surely it's premature )

Weirdly enough, Chrome Canary seems to be able to optimize the single
WeakMap approach pretty well, with a gap of 1.25X faster perfs on multiple
WMs.

the bench: http://jsperf.com/abstract-reference

the multiple WeakMaps approach:

```js
var PrivateCoordinatesMWM = (function () {

var
privates = {
x: new WeakMap,
y: new WeakMap
}
;

function __(self, key, value) {
return arguments.length === 3 ?
(privates[key].set(self, value), value) :
privates[key].get(self);
}

function PrivateCoordinatesMWM(x, y) {
__(this, 'x', x);
__(this, 'y', y);
}

PrivateCoordinatesMWM.prototype.toString = function toString() {
return ''.concat(
'[', __(this, 'x'), 'x', __(this, 'y'), ']'
);
};

return PrivateCoordinatesMWM;

}());
```

and the single WeakMap approach:

```js
var PrivateCoordinatesSWM = (function () {

var privates = new WeakMap;

function __(self, key, value) {
var pvt = privates.get(self);
if (arguments.length === 3) {
if (!pvt) {
privates.set(self, pvt = {});
}
return (pvt[key] = value);
}
return pvt && pvt[key];
}

function PrivateCoordinatesSWM(x, y) {
__(this, 'x', x);
__(this, 'y', y);
}

PrivateCoordinatesSWM.prototype.toString = function toString() {
return ''.concat(
'[', __(this, 'x'), 'x', __(this, 'y'), ']'
);
};

return PrivateCoordinatesSWM;

}());
```

Best Regards


On Wed, Oct 22, 2014 at 7:59 AM, Andreas Rossberg <rossberg at google.com>
Post by Domenic Denicola
Post by Mark S. Miller
in saying that [weak maps] are not designed to work efficiently in that
manner.
Post by Mark S. Miller
Actually, they are. The problem is that initial implementations date from
before we appreciated the need for the transposed representation. The
best
Post by Mark S. Miller
thing TC39 can do for the future of WeakMaps is to proceed assuming the
transposed representation.
While I sympathise, let me clarify that this still remains a
conjecture. AFAIK, nobody has proved it empirically in a JS
implementation yet, we don't know in detail how complex such an
implementation would be, and what side effects it might have on
general performance (e.g., via added polymorphism). It's most likely
not as easy or as clear a win as you may think it is.
/Andreas
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/efa8db2b/attachment.html>
Andreas Rossberg
2014-10-22 13:46:04 UTC
Permalink
Post by Andrea Giammarchi
FWIW the Multiple WeakMaps approach seems to be 2X ( Chrome ) up to 3X ( FF
Nightly ) faster than single WeakMap approach but I don't know how much
WeakMap instances cost in terms of GC operations so this bench might be very
pointless ( and surely it's premature )
Weirdly enough, Chrome Canary seems to be able to optimize the single
WeakMap approach pretty well, with a gap of 1.25X faster perfs on multiple
WMs.
If you really want to micro-benchmark, then perhaps at least use more
reasonable code that avoids unnecessary indirections distorting the
results. That is:

```js
var PrivateCoordinatesMWM = (function () {
var x_ = new WeakMap
var y_ = new WeakMap

function PrivateCoordinatesMWM(x, y) {
x_.set(this, x)
y_.set(this, y)
}

PrivateCoordinatesMWM.prototype.toString = function toString() {
return ''.concat('[', x_.get(this), y_.get(this), ']')
};

return PrivateCoordinatesMWM
}())
```

This also happens to be more readable. ;)

/Andreas
Andrea Giammarchi
2014-10-22 14:34:42 UTC
Permalink
I was trying to actually use same amount of indirections, since that's
probably more likely how real code might look like but you are right, hand
crafting per each case the pattern we have a different winner, which is the
single WeakMap instead of the multiple one.

I guess the reason is the double x/y.set and x/y.get VS only one, here the
new bench:
http://jsperf.com/abstract-reference/2

and here the hand crafted code:

```js
var result = [];

// Multiple WeakMaps approach
var PrivateCoordinatesMWM = (function () {

var
_x = new WeakMap,
_y = new WeakMap
;

function PrivateCoordinatesMWM(x, y) {
_x.set(this, x);
_y.set(this, y);
}

PrivateCoordinatesMWM.prototype.toString = function toString() {
return ''.concat(
'[', _x.get(this), 'x', _y.get(this), ']'
);
};

return PrivateCoordinatesMWM;

}());

// Single WeakMap approach
var PrivateCoordinatesSWM = (function () {

var _wm = new WeakMap;

function PrivateCoordinatesSWM(x, y) {
_wm.set(this, {x: x, y: y});
}

PrivateCoordinatesSWM.prototype.toString = function toString() {
var _pvt = _wm.get(this);
return ''.concat(
'[', _pvt.x, 'x', _pvt.y, ']'
);
};

return PrivateCoordinatesSWM;

}());
```

Regards


On Wed, Oct 22, 2014 at 2:46 PM, Andreas Rossberg <rossberg at google.com>
Post by Domenic Denicola
Post by Andrea Giammarchi
FWIW the Multiple WeakMaps approach seems to be 2X ( Chrome ) up to 3X (
FF
Post by Andrea Giammarchi
Nightly ) faster than single WeakMap approach but I don't know how much
WeakMap instances cost in terms of GC operations so this bench might be
very
Post by Andrea Giammarchi
pointless ( and surely it's premature )
Weirdly enough, Chrome Canary seems to be able to optimize the single
WeakMap approach pretty well, with a gap of 1.25X faster perfs on
multiple
Post by Andrea Giammarchi
WMs.
If you really want to micro-benchmark, then perhaps at least use more
reasonable code that avoids unnecessary indirections distorting the
```js
var PrivateCoordinatesMWM = (function () {
var x_ = new WeakMap
var y_ = new WeakMap
function PrivateCoordinatesMWM(x, y) {
x_.set(this, x)
y_.set(this, y)
}
PrivateCoordinatesMWM.prototype.toString = function toString() {
return ''.concat('[', x_.get(this), y_.get(this), ']')
};
return PrivateCoordinatesMWM
}())
```
This also happens to be more readable. ;)
/Andreas
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/7a711786/attachment.html>
Mark S. Miller
2014-10-22 14:45:31 UTC
Permalink
On Tue, Oct 21, 2014 at 11:59 PM, Andreas Rossberg <rossberg at google.com>
Post by Domenic Denicola
Post by Mark S. Miller
in saying that [weak maps] are not designed to work efficiently in that
manner.
Post by Mark S. Miller
Actually, they are. The problem is that initial implementations date from
before we appreciated the need for the transposed representation. The
best
Post by Mark S. Miller
thing TC39 can do for the future of WeakMaps is to proceed assuming the
transposed representation.
While I sympathise, let me clarify that this still remains a
conjecture. AFAIK, nobody has proved it empirically in a JS
implementation yet, we don't know in detail how complex such an
implementation would be, and what side effects it might have on
general performance (e.g., via added polymorphism). It's most likely
not as easy or as clear a win as you may think it is.
The following code is an existence proof of sorts that, given only the
WeakMap mechanisms you've already built + one bit of magic whose
feasibility I hope we don't need to debate. I use ES5 here to make clear
that no other magic or unengineered features are assumed.


var WeakMap;

(function() {
'use strict';

var SlowWeakMap = WeakMap;

function FastWeakMap() {
var token = Object.freeze(Object.create(null));
return Object.freeze({
get: function(key) {
var shadow = key.[[Shadow]];
return shadow ? shadow.get(token) : void 0;
},
set: function(key, value) {
var shadow = key.[[Transposer]]
if (!shadow) {
shadow = new SlowWeakMap();
key.[[Transposer]] = shadow;
}
shadow.set(token, value);
},
clear: function() {
token = Object.freeze(Object.create({}));
}
});
}

// Don't do this until it is a complete shim
// WeakMap = FastWeakMap;

}());



The magic is that each object, whether frozen or not, would need in effect
one extra internal mutable [[Shadow]] property.

Clearly, this expository implementation is suboptimal in many ways. But it
demonstrates the following:

* It provides the full complexity measure gains that a realistic
implementation would have.

* For each of these objects, an extra SlowWeakMap instance is allocated as
its shadow.

* For each access, an extra indirection through this SlowWeakMap is
therefore needed.

* Only objects that have been used as keys in FastWeakMaps would ever have
their [[Shadow]] set, so this could also be allocated on demand, given only
a bit saying whether it is present. Besides this storage of this bit, there
is no other effect or cost on any non-weakmap objects.

* Since non-weakmap code doesn't need to test this bit, there is zero
runtime cost on non-weakmap code.

* Whether an object has been used as a key or not (and therefore whether an
extra shadow has been allocated or not), normal non-weak property lookup on
the object is unaffected, and pays no additional cost.



A realistic implementation should seek to avoid allocating the extra shadow
objects. However, even if not, we are much better off with the above scheme
than we are with the current slow WeakMap.

Of course, we should proceed towards realistic implementations asap and get
actual empirical data. But the above demonstration establishes that the
issue in this thread should be considered settled.
--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/c256c540/attachment.html>
Mark S. Miller
2014-10-22 14:48:25 UTC
Permalink
Should be



var WeakMap;

(function() {
'use strict';

var SlowWeakMap = WeakMap;

function FastWeakMap() {
var token = Object.freeze(Object.create(null));
return Object.freeze({
get: function(key) {
var shadow = key.[[Shadow]];
return shadow ? shadow.get(token) : void 0;
},
set: function(key, value) {
var shadow = key.[[Shadow]]
if (!shadow) {
shadow = new SlowWeakMap();
key.[[Shadow]] = shadow;
}
shadow.set(token, value);
},
clear: function() {
token = Object.freeze(Object.create({}));
}
});
}

// Don't do this until it is a complete shim
// WeakMap = FastWeakMap;

}());
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/e89bc85b/attachment.html>
Steve Fink
2014-10-22 20:44:47 UTC
Permalink
Post by Mark S. Miller
* Only objects that have been used as keys in FastWeakMaps would ever
have their [[Shadow]] set, so this could also be allocated on demand,
given only a bit saying whether it is present. Besides this storage of
this bit, there is no other effect or cost on any non-weakmap objects.
* Since non-weakmap code doesn't need to test this bit, there is zero
runtime cost on non-weakmap code.
* Whether an object has been used as a key or not (and therefore
whether an extra shadow has been allocated or not), normal non-weak
property lookup on the object is unaffected, and pays no additional cost.
Maybe it's because I work on a garbage collector, but I always think of
the primary cost of WeakMaps as being the GC. The above analysis doesn't
take GC into account.

In the straightforward iterative implementation, you record all of the
live WeakMaps found while scanning through the heap. Then you go through
them, checking each key to see if it is live. For each such key, you
recursively mark the value. This marking can discover new live WeakMaps,
so you iterate to a fixed point.

In the current web, this implementation seems to work fine. The worst
case is O(n^2) in the size of the heap, which is pretty much fatal if
you ever hit it. But that requires lots of paths through multiple
WeakMaps, and in practice, it seems WeakMaps aren't being used much.
I've never seen our WeakMap marking phase show up as a significant cost.

For an algorithmically more robust solution, you could add a check
whenever marking an object. The check would test whether the object is
(or might be) used as a WeakMap key. This would slow down marking all
objects, so in practice you want to be clever about avoiding the test.

Anyway, my point is that WeakMaps have serious GC ramifications,
possibly extending to non-key objects, and any performance impact
analysis of using WeakMaps more extensively is incomplete without
considering GC costs.
Post by Mark S. Miller
A realistic implementation should seek to avoid allocating the extra
shadow objects. However, even if not, we are much better off with the
above scheme than we are with the current slow WeakMap.
Perhaps. But multiple WeakMaps introduce the potential for many more
cycles than a single WeakMap. So I think a final conclusion is premature.
Mark Miller
2014-10-22 21:26:43 UTC
Permalink
Post by Steve Fink
Post by Mark S. Miller
* Only objects that have been used as keys in FastWeakMaps would ever
have their [[Shadow]] set, so this could also be allocated on demand,
given only a bit saying whether it is present. Besides this storage of
this bit, there is no other effect or cost on any non-weakmap objects.
* Since non-weakmap code doesn't need to test this bit, there is zero
runtime cost on non-weakmap code.
* Whether an object has been used as a key or not (and therefore
whether an extra shadow has been allocated or not), normal non-weak
property lookup on the object is unaffected, and pays no additional cost.
Maybe it's because I work on a garbage collector, but I always think of
the primary cost of WeakMaps as being the GC. The above analysis doesn't
take GC into account.
I should have been more explicit, but GC costs are almost my entire point.
These costs aside, my FastWeakMaps are more expensive in all ways than
SlowWeakMaps, though only by a constant factor, since each FastWeakMap
operation must also perform the corresponding SlowWeakMap operation.
Post by Steve Fink
In the straightforward iterative implementation, you record all of the
live WeakMaps found while scanning through the heap. Then you go through
them, checking each key to see if it is live. For each such key, you
recursively mark the value. This marking can discover new live WeakMaps,
so you iterate to a fixed point.
That is when you find yourself doing an ephemeron collection. The point of
the transposed representation is to collect most ephemeron garbage using
conventional collection. Consider

var fastField = new FastWeakMap();
var slowField = new SlowWeakMap();

var transientKey = {};

var fastValue = {};
var slowValue = {};

fastField.set(key, fastValue);
slowField.set(key, slowValue);

transientKey = void 0;
fastValue = void 0;
slowValue = void 0;


At this assume that the old value of transientKey is really garbage, but
that fastField and slowField are still reachable. Clearly, both the old
value of fastValue and the old value of slowValue are now garbage and may
be collected. Let's see what work the collector need to do to collect these.

First comes the conventional mark phase: fastField and slowField both get
marked. slowField is itself a non-transposed weakmap, so when we mark it we
also put it on the queue of weakmaps to be ephemeron collected.
fastField internally is not a weakmap, nor does it point at one. It's only
per-instance state is the token, so this gets marked as well.

Now comes the ephemeron mark phase. The only non-transposed weakmap to be
considered is slowField. The non-transposed weakmap serving as
transientKey's shadow never got marked because transientKey never got
marked. fastValue is only reachable from transientKey's shadow, so it also
never gets marked.

Here's the key important thing: In a generational collector, at this point
we'd typically postpone ephemeron collection. To do so, we would complete
the mark phase conventionally, by simply marking the values held by
slowField. This marks slowValue, causing it to get promoted to the next
older generation. THIS IS EXPENSIVE.

Finally, when we can no longer postpone ephemeron collection, when
ephemeron-marking slowField, we'd notice that transientKey didn't get
marked, so we wouldn't mark slowValue.

The counterpoint is the shadow weakmaps, when engaged in ephemeron
collection must still check whether their keys -- to tokens within the
FastWeakMaps -- are still reachable. Typically they will be. This has two
counter-counterpoints:

* For all practical use patterns we've identified, the WeakMap either lives
longer than its keys, or their lifespan is comparable. When the WeakMap is
known to necessarily live longer than its keys, as for class-private state,
then those shadow properties can even be exempted from the key-mark-check.

* Prior to emphemeron collection, the shadow weakmaps (and the values they
carry) get promoted to older generations only along with the object they
shadow.
Post by Steve Fink
In the current web, this implementation seems to work fine. The worst
case is O(n^2) in the size of the heap, which is pretty much fatal if
you ever hit it. But that requires lots of paths through multiple
WeakMaps, and in practice, it seems WeakMaps aren't being used much.
I've never seen our WeakMap marking phase show up as a significant cost.
Chicken and egg. If WeakMaps are used for private state (and trademarks
and...), they will be used a lot. But they will only be used for those
things if it isn't fatally slow to do so.
Post by Steve Fink
For an algorithmically more robust solution, you could add a check
whenever marking an object. The check would test whether the object is
(or might be) used as a WeakMap key. This would slow down marking all
objects, so in practice you want to be clever about avoiding the test.
Yeah, I'm very curious about whether this can be made cheap enough that
implementations would be willing to do it. If so, then everything is much
better, whether we transpose the representation or not.
Post by Steve Fink
Anyway, my point is that WeakMaps have serious GC ramifications,
possibly extending to non-key objects, and any performance impact
analysis of using WeakMaps more extensively is incomplete without
considering GC costs.
Exactly! I should have been clearer that these were the only costs I am
concerned about here. Regarding all other costs, my example code only adds
expense.
Post by Steve Fink
Post by Mark S. Miller
A realistic implementation should seek to avoid allocating the extra
shadow objects. However, even if not, we are much better off with the
above scheme than we are with the current slow WeakMap.
Perhaps. But multiple WeakMaps introduce the potential for many more
cycles than a single WeakMap. So I think a final conclusion is premature.
I think this is a good point that I missed in my analysis. We should indeed
think hard about this. Given that, in the end I must agree that a
conclusion is premature. Please let's proceed to build some experiments!
--
Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/06f929b1/attachment.html>
Rick Waldron
2014-10-22 21:46:00 UTC
Permalink
Post by Mark Miller
Post by Steve Fink
Post by Mark S. Miller
* Only objects that have been used as keys in FastWeakMaps would ever
have their [[Shadow]] set, so this could also be allocated on demand,
given only a bit saying whether it is present. Besides this storage of
this bit, there is no other effect or cost on any non-weakmap objects.
* Since non-weakmap code doesn't need to test this bit, there is zero
runtime cost on non-weakmap code.
* Whether an object has been used as a key or not (and therefore
whether an extra shadow has been allocated or not), normal non-weak
property lookup on the object is unaffected, and pays no additional
cost.
Maybe it's because I work on a garbage collector, but I always think of
the primary cost of WeakMaps as being the GC. The above analysis doesn't
take GC into account.
I should have been more explicit, but GC costs are almost my entire point.
These costs aside, my FastWeakMaps are more expensive in all ways than
SlowWeakMaps, though only by a constant factor, since each FastWeakMap
operation must also perform the corresponding SlowWeakMap operation.
Post by Steve Fink
In the straightforward iterative implementation, you record all of the
live WeakMaps found while scanning through the heap. Then you go through
them, checking each key to see if it is live. For each such key, you
recursively mark the value. This marking can discover new live WeakMaps,
so you iterate to a fixed point.
That is when you find yourself doing an ephemeron collection. The point of
the transposed representation is to collect most ephemeron garbage using
conventional collection. Consider
var fastField = new FastWeakMap();
var slowField = new SlowWeakMap();
var transientKey = {};
var fastValue = {};
var slowValue = {};
fastField.set(key, fastValue);
slowField.set(key, slowValue);
I don't mean to nit-pick, but "key" is "transientKey", right?

Rick
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/aeff4a7a/attachment-0001.html>
Mark Miller
2014-10-22 21:59:29 UTC
Permalink
Please do nitpick. I wrote this in too much of a hurry and it is something
that needs care.

In any case, yes, transientKey.

On Wed, Oct 22, 2014 at 2:46 PM, Rick Waldron <waldron.rick at gmail.com>
Post by Rick Waldron
Post by Mark Miller
Post by Steve Fink
Post by Mark S. Miller
* Only objects that have been used as keys in FastWeakMaps would ever
have their [[Shadow]] set, so this could also be allocated on demand,
given only a bit saying whether it is present. Besides this storage of
this bit, there is no other effect or cost on any non-weakmap objects.
* Since non-weakmap code doesn't need to test this bit, there is zero
runtime cost on non-weakmap code.
* Whether an object has been used as a key or not (and therefore
whether an extra shadow has been allocated or not), normal non-weak
property lookup on the object is unaffected, and pays no additional
cost.
Maybe it's because I work on a garbage collector, but I always think of
the primary cost of WeakMaps as being the GC. The above analysis doesn't
take GC into account.
I should have been more explicit, but GC costs are almost my entire
point. These costs aside, my FastWeakMaps are more expensive in all ways
than SlowWeakMaps, though only by a constant factor, since each FastWeakMap
operation must also perform the corresponding SlowWeakMap operation.
Post by Steve Fink
In the straightforward iterative implementation, you record all of the
live WeakMaps found while scanning through the heap. Then you go through
them, checking each key to see if it is live. For each such key, you
recursively mark the value. This marking can discover new live WeakMaps,
so you iterate to a fixed point.
That is when you find yourself doing an ephemeron collection. The point
of the transposed representation is to collect most ephemeron garbage using
conventional collection. Consider
var fastField = new FastWeakMap();
var slowField = new SlowWeakMap();
var transientKey = {};
var fastValue = {};
var slowValue = {};
fastField.set(key, fastValue);
slowField.set(key, slowValue);
I don't mean to nit-pick, but "key" is "transientKey", right?
Rick
--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/971be457/attachment.html>
Steve Fink
2014-10-22 22:06:01 UTC
Permalink
On Wed, Oct 22, 2014 at 1:44 PM, Steve Fink <sphink at gmail.com
Post by Mark S. Miller
* Only objects that have been used as keys in FastWeakMaps would
ever
Post by Mark S. Miller
have their [[Shadow]] set, so this could also be allocated on
demand,
Post by Mark S. Miller
given only a bit saying whether it is present. Besides this
storage of
Post by Mark S. Miller
this bit, there is no other effect or cost on any non-weakmap
objects.
Post by Mark S. Miller
* Since non-weakmap code doesn't need to test this bit, there is
zero
Post by Mark S. Miller
runtime cost on non-weakmap code.
* Whether an object has been used as a key or not (and therefore
whether an extra shadow has been allocated or not), normal non-weak
property lookup on the object is unaffected, and pays no
additional cost.
Maybe it's because I work on a garbage collector, but I always think of
the primary cost of WeakMaps as being the GC. The above analysis doesn't
take GC into account.
I should have been more explicit, but GC costs are almost my entire
point. These costs aside, my FastWeakMaps are more expensive in all
ways than SlowWeakMaps, though only by a constant factor, since each
FastWeakMap operation must also perform the corresponding SlowWeakMap
operation.
Ah, sorry, I totally missed your point.
In the straightforward iterative implementation, you record all of the
live WeakMaps found while scanning through the heap. Then you go through
them, checking each key to see if it is live. For each such key, you
recursively mark the value. This marking can discover new live WeakMaps,
so you iterate to a fixed point.
That is when you find yourself doing an ephemeron collection. The
point of the transposed representation is to collect most ephemeron
garbage using conventional collection. Consider
Ok, I get it now, and completely agree with your analysis, with the
caveat that supporting [[Shadow]] gives me the heebie-jeebies. It turns
a read into a write, for one thing. (The read of the key, I mean.) Could
the extra shadow table be kept separate from the key object? I know!
Let's use a WeakMap! :-)
Here's the key important thing: In a generational collector, at this
point we'd typically postpone ephemeron collection. To do so, we would
complete the mark phase conventionally, by simply marking the values
held by slowField. This marks slowValue, causing it to get promoted to
the next older generation. THIS IS EXPENSIVE.
Yes, this is a big deal.
In the current web, this implementation seems to work fine. The worst
case is O(n^2) in the size of the heap, which is pretty much fatal if
you ever hit it. But that requires lots of paths through multiple
WeakMaps, and in practice, it seems WeakMaps aren't being used much.
I've never seen our WeakMap marking phase show up as a significant cost.
Chicken and egg. If WeakMaps are used for private state (and
trademarks and...), they will be used a lot. But they will only be
used for those things if it isn't fatally slow to do so.
Yes, I fully expect WeakMaps to start mattering soon-ish, though I'm
still procrastinating on doing anything about our current implementation.
For an algorithmically more robust solution, you could add a check
whenever marking an object. The check would test whether the object is
(or might be) used as a WeakMap key. This would slow down marking all
objects, so in practice you want to be clever about avoiding the test.
Yeah, I'm very curious about whether this can be made cheap enough
that implementations would be willing to do it. If so, then everything
is much better, whether we transpose the representation or not.
We'll probably all end up at some messy point in the middle. Maybe a
fast initial pass without the checks. It'll be something that depends on
a bunch of assumptions for normal-case performance, but doesn't
completely break down in the pathological cases.
Anyway, my point is that WeakMaps have serious GC ramifications,
possibly extending to non-key objects, and any performance impact
analysis of using WeakMaps more extensively is incomplete without
considering GC costs.
Exactly! I should have been clearer that these were the only costs I
am concerned about here. Regarding all other costs, my example code
only adds expense.
If I had read more closely, I probably would have noticed that...

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/6e8e86ce/attachment.html>
Kevin Smith
2014-10-23 01:30:11 UTC
Permalink
Post by Domenic Denicola
My initial worries are largely around the ergonomics---both for authors
and implementers---if this is our solution for private state. In
particular, I don't think having to create a new WeakMap for every private
"member" is very author-friendly.
Even without "private" sugar, I think we could use some Proxy-foo to make
this more pleasant:

const Private = new Proxy({}, get() { return new WeakMap });

const { _x, _y } = Private;
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/a0db337a/attachment.html>
Isiah Meadows
2014-10-22 20:12:40 UTC
Permalink
I know that this could clearly work for implementing private arrays, etc.
as well, but what about private integer or booleans?

```js
let _x = 0;
let _y = false;

class Foo {
constructor(x, y) {
this::_x = x;
this::_y = y;
}

// ...
}
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20141022/c18a3d46/attachment.html>
Tab Atkins Jr.
2014-10-22 23:05:50 UTC
Permalink
I know that this could clearly work for implementing private arrays, etc. as
well, but what about private integer or booleans?
```js
let _x = 0;
let _y = false;
class Foo {
constructor(x, y) {
this::_x = x;
this::_y = y;
}
// ...
}
You misunderstand the proposal a bit. In `x::y`, the y is an object
that is asked to respond in a special way. When `y` is a WeakMap, it
responds to `x::y = z;` by calling `y.set(x, z)` on itself.

You can store whatever you want in there; the `z` value can be
anything, including numbers or booleans. But the `y` object needs to
be something that knows how to respond to the bind operator.

(Similarly, if `y` is a function, by default it responds to `x::y(z)`
by calling itself with its `this` set to `x`. This makes it act
similarly to `x.y(z)`, but without having to actually define `y` as a
property on `x`.)

~TJ
Loading...