Closed Bug 1172313 Opened 9 years ago Closed 8 months ago

JS Proxy() is very slow

Categories

(Core :: JavaScript Engine: JIT, defect, P5)

defect

Tracking

()

RESOLVED WORKSFORME
Tracking Status
firefox41 --- affected

People

(Reporter: davemgarrett, Unassigned)

References

(Blocks 2 open bugs)

Details

(Whiteboard: DUPEME)

Attachments

(2 files)

Attached file test case
var testdata = new Uint32Array(testdatasize);
var proxiedtestdata = new Proxy(testdata, {
    get: function(selfarray,i) {
        return selfarray[i];
    }
});

The above usage of JS proxies is very slow.

Test in new Nightly profile.
Load the attached reduced testcase.
Read the result when it finishes.
I'm seeing the proxied version being orders of magnitude slower.

Some overhead is reasonable. Being 50 or 100 times slower makes this unusable.

The use case here is that I would like to be able to use JS proxies to create array-like structures to simplify reading binary files. This is simply too slow to be usable.

In fact, the larger the testdata size, the slower it gets relative to the unproxied version as the unproxied version or a simple class with a get(i) function are extremely fast in comparison.

I'm assuming this means Proxy() isn't JITed in the slightest?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
Note that on a computer that's faster than the old laptop I submitted this on, the two fast cases can run in <1ms and break my simple test timing with Date.now(). You can bump up the data size to fix that if you need to. (first var)

Tested on both Linux and Windows, by the way.
OS: Unspecified → All
Hardware: Unspecified → All
Meta-object protocol operations -- property accesses and the like -- on proxies aren't optimized in the JITs at present: instead you'll hit the "slow" and generic code paths.  This will change at some point, but it's not likely to happen soon.  So it's more or less "expected" right now that these operations would be slow.

I couldn't say for sure when we're likely to change this.  Proxies aren't yet widely implemented, ergo aren't widely used on the web, and so it's likely our optimization efforts will focus on things that *are* widely used for awhile.  But I expect we eventually will loop back around and optimize this, just not any time soon.  I'd be surprised if it happened within the next year, but I could be proven wrong.
Hmm, I think proxies are being considered to solve various problems in frontend code (is that the right term?). For instance I believe the approach in bug 906076 is based on them, to avoid breaking every extension that touches session restore. I don't know if the e10s add-on interposition/shims ended up relying on them, but I remember it coming up during development.

That's just an observation of course. I don't know if these things are particularly hot or JITable in the first place - but it would kind of suck if they started slowing down the browser in the presence of reasonably written extensions.
Proxies are in fact jitted to some extent.  We have inline caches to jump directly into the proxy handler C++.

But at that point for scripted proxies we end up with calls back out to JS, etc.  It's quite trivial for this to become 50-100 times than the single memory read that you get for an actual typed array get.  In fact, if the slowdown is only 50x we're doing quite well.

There has been some talk about doing something here for scripted proxies specifically but the proxy operations are just _expensive_ per spec no matter how you look at it.  Even if the actual get function here is inlined, the sanity checking proxies are required to perform in addition to making that call into the handler (see http://people.mozilla.org/~jorendorff/es6-draft.html#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver steps 11 and 13) is just not easy to make fast on the scale of "just do a memory read".
Whiteboard: DUPEME
(In reply to Not doing reviews right now from comment #5)
> There has been some talk about doing something here for scripted proxies
> specifically but the proxy operations are just _expensive_ per spec no
> matter how you look at it.

That's disappointing to hear. I wish there was a simpler spec just for getters/setters so that array-like stuff could be written efficiently.
The only way to make it simpler would be to have some declarative way to indicate that the get/set is just getting forwarded to the target instead of writing them as functions.

I _guess_ we could try pattern-matching on direct forwarding or something.  But it's not clear to me whether this is in fact the pattern that would get used the most.  Why bother with a proxy which just always transparently passes on its get/set?
(In reply to Not doing reviews right now from comment #7)
> Why bother with a proxy which just always transparently
> passes on its get/set?

To be clear, the reduced testcase is of course just to demonstrate perf by doing the exact same thing, but with the overhead of a Proxy(). It's your standard nonsense reduced benchmark, but it demonstrates the perf problem.

The primary use-case I was looking into was implementing an endian-aware Uint32Array to load from files on multiple platforms. (the type-specific arrays are native-endian only; DataView can do non-native-endian, but is also very slow: bug 1065894) I also have the odd use-case of needing to implement a Uint48Array to load from a file. (IPv6 network prefixes) What I ended up with works quite well now, but being able to create custom typed arrays would make cleaner code instead of having to go back and forth between 'array[i]' and 'array.get(i)' depending on the structure. It means you can't generically use/store/pass a ref to an array as simply without the user function knowing which it is. It's not crucial, though. I got what I needed working without it just fine, and for my specific case it'd just be very trivial syntatic sugar at this point. It just seems like this sort of thing would be the perfect job for Proxy(), especially if the use-case was more complicated, but the performance just rules it out. Oh well...
> It's your standard nonsense reduced benchmark, but it demonstrates the perf problem.

Sure.  But the point is, speeding up this reduced benchmark in the obvious way would not speed up real use cases (a not uncommon problem with reduced benchmarks).

> but being able to create custom typed arrays would make cleaner code

I think the long-term solution for this particular use case is typed objects...  But yes, the problem with proxies as specced is that you can try to shoot yourself in the foot in oh so many ways and the checks that you're not doing that are a drag.  They're great for being generic, but not for being fast.  :(
Blocks: 1245974
Priority: -- → P5
Blocks: es6perf
It seems something about Proxies are just inherently slow, given this test case, access through proxy is significantly slower.

  function get (target, prop) {
    return target[prop];
  }
  
  const o = { val: 'value' };
  const p = new Proxy(o, { get });

  o.val;
  get(o, 'val');
  p.val;


Even with a case that doesn't have a defined get handler, performance through proxies are still significantly slower (even slower than direct usage of the get function above).

  const o = { val: 'value' };
  const p = new Proxy(o, {});

  o.val;
  p.val;

This is a problem since it may be desirable to only have a hook for say Set and not Get, or vice versa. In that scenario it seems feasible that performance for non hooked operations should be close to direct operation on the target object.

Here's a JS perf https://jsperf.com/proxy-without-handlers-vs-object-access
See Also: → 1462843
Severity: normal → S3

Nightly:
testcase:
same results? -> true (141044759965964)
base time -> 0ms total
proxied time -> 6ms total (Infinity% of base time)
getterclass time -> 0ms total (NaN% of base time)

Large testcase:
same results? -> true (1407754058880850)
base time -> 2ms total
proxied time -> 41ms total (2050% of base time)
getterclass time -> 2ms total (100% of base time)

Chrome:
testcase:
same results? -> true (141044759965964)
base time -> 2ms total
proxied time -> 16ms total (800% of base time)
getterclass time -> 1ms total (50% of base time)

Large testcase:
same results? -> true (1407754058880850)
base time -> 8ms total
proxied time -> 149ms total (1862.5% of base time)
getterclass time -> 11ms total (137.5% of base time)

Looks like we are fast enough. Closing this. Please file a new bug if there are other things that can be fixed.

Status: NEW → RESOLVED
Closed: 8 months ago
Resolution: --- → WORKSFORME
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: