Le 05/02/2013 13:52, Sam Tobin-Hochstadt a écrit :
On Tue, Feb 5, 2013 at 7:03 AM, David Bruant<bruan...@gmail.com>  wrote:
I like the current API better because it allows for a cleaner pairing of pre
and post-traps, including the ability to share private intermediate state
through closure capture.
I have to admit, I'm a bit sad to loose that too. But that's the price to
pay to get rid of invariant checks I think. It remains possible for pre/post
trap to share info through the handler.
I've been holding off on this because I know that Mark is still
working on notification proxies, but I think this short discussion
encapsulates exactly why notification proxies are a bad idea.  The big
win of notification proxies is that it reduces the cost and complexity
of invariant checks [1]. However, I believe this cost is small, and
more importantly, not that relevant.
Another justification from my experience is that a lot of traps end with:
    return Reflect.trap(...args)
So notification proxies also make implicit what is otherwise boilerplate. That's an important point (more below).

As evidence that this cost is small, in our work on chaperones in
Racket [2], a system that's very similar to proxies [3], we measured
the overhead of the invariant checking required.  In real programs,
even when the proxy overhead was more than *half* the total runtime,
the invariant checks never went above 1% of runtime.  Further, the
design of chaperones, in combination with much greater use of
immutable data in Racket, means that many *more* invariant checks were
performed than would be in a comparable JS system, and the Racket
invariant checks would be significantly *more* expensive.
Interesting stats. Thanks for sharing.

Even more importantly, optimizing the invariant checks is focusing on
the wrong use case.  Regardless of our preferences, very little JS
data is immutable, or requires any invariant checks at all.
At the very least, the engine has to test the following after most traps:
target.[[GetOwnPropertyDescriptor]](name).[[Get]]('configurable') === false
If the property is non-configurable, more invariant checks are needed. Otherwise, the code goes on, but it was necessary to test this before knowing it was possible to go on. I'll call this test "pre-invariant check"

So even "no invariant checks" means at least one "pre-invariant check" per-invocation. Since most traps end with "return Reflect.trap(...args)", this test feels even more stupid.
Here is what happens in the getOwnPropertyDescriptor trap case:
1) call the trap. It most likely ends with "return Reflect.getOwnPropertyDescriptor(...args)" 2) The runtime does its own Reflect.getOwnPropertyDescriptor(...args) call and compare its result with the one returned from the trap. 3) It obviously notices both descriptors are compatible (duh! they are the same because no code could modify it between the trap return and pre-invariant check)

Most traps have an equivalent story. Only the first step changes because it's a different trap, but by design of the invariants, the "duh!" in 3) remains.

We might count on static analysis or such, but I've been told enough on the list not to rely too much on that. Opinions on that are welcome.

We spend a lot of time focusing on re-implementations of built-in ES and DOM
APIs, which often are non-configurable or non-writable, but this is
not the common case in user-written JS.  Whether it's building
free-standing exotic objects or wrapping existing ones, it's very
likely that this will continue to be the case with proxy-implemented
objects.  We should focus on that case.
I could not agree more. For me, getting rid of invariant checks mostly means getting rid of the above test. Since most of my objects don't need invariant checks (because they end with "return Reflect.trap"), I don't know why I should be paying the above test every single time a trap exits. I already know what I want, I made it clear in my code, why am I paying a tax at all, even 0.5%?

In these common cases, I believe that notification proxies are at a
significant disadvantage.  Notification proxies require that all
communication between the handler and the result of an operation
operates via mutation of the target.
This has several problems.
First, it's a tricky pattern that every proxy programmer has to learn,
increasing the burden for an already complex API.
I'm balanced on that point. When writing a set trap, the trap is likely to end with "Reflect.set(target, name, value, receiver)", so when I write handler code, I already naturally communicate with the target. But, I agree that there are cases where code returning a different value will have to set the value to the target. I'm not entirely happy with this, but I wonder if it's because I'm just used to direct proxies. In the "notification proxy" way of thinking, traps are a notification mechanism; their return value doesn't matter, so maybe it's normal that communication with the outside occurs through the target.

In direct proxies, the engine has to fetch information on the target (whether to check/pre-check invariants or to return a value). It is not absurd to ask the programmer to put explicitly on the target what the engine should find in it.

Second, it forces
the use of the "shadow target" pattern in any wrapper, doubling the
number of allocations required.
I don't understand why more shadow targets would be necessary than with direct proxies.

Third, the complexity of this pattern
will make proxies that use it harder for engines to optimize. Again,
our experience with Racket is relevant here, and we were able to
achieve a 4.5x speedup on microbenchmarks (a bubble-sort of a proxied
array) by adding simple support in the JIT for proxies.
Isn't it too early to say this pattern is hard to optimize? or harder than direct proxies?

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

Reply via email to