On notification proxies

David Bruant bruant.d at gmail.com
Tue Feb 5 07:29:07 PST 2013

Le 05/02/2013 13:52, Sam Tobin-Hochstadt a écrit :
> On Tue, Feb 5, 2013 at 7:03 AM, David Bruant<bruant.d at 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 
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) 

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?


More information about the es-discuss mailing list