开发者

Why is new slow?

The benchmark:

JsPerf

The invariants:

var f = function() { };

var g = function() { return this; }

The tests:

Below in order of expected speed

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

Actual speed :

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

The question:

  1. When you swap f and g for inline anonymous functions. Why is the new (test 4.) test slower?

Update:

What specifically causes the new to be slower when f and g are inlined.

I'm intereste开发者_运维知识库d in references to the ES5 specification or references to JagerMonkey or V8 source code. (Feel free to link JSC and Carakan source code too. Oh and the IE team can leak Chakra source if they want to).

If you link any JS engine source, please explain it.


The main difference between #4 and all other cases is that the first time when you use a closure as a constructor is always quite expensive.

  1. It is always handled in V8 runtime (not in generated code) and transition between compiled JS code and C++ runtime is quite expensive. Subsequent allocations usually are handled in generated code. You can take a look at Generate_JSConstructStubHelper in builtins-ia32.cc and notice that is falls through to the Runtime_NewObject when closure has no initial map. (see http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138)

  2. When closure is used as a constructor for the first time V8 has to create a new map (aka hidden class) and assign it as an initial map for that closure. See http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266. Important thing here is that maps are allocated in a separate memory space. This space can't be cleaned by a fast partial scavenge collector. When map space overflows V8 has to perform relatively expensive full mark-sweep GC.

There are couple of other things happening when you use closure as a constructor for the first time but 1 and 2 are the main contributors to the slowness of test case #4.

If we compare expressions #1 and #4 then differences are:

  • #1 does not allocate a new closure every time;
  • #1 does not enter runtime every time: after closure gets initial map construction is handled in the fast path of generated code. Handling the whole construction in generated code is much faster then going back and forth between runtime and generated code;
  • #1 does not allocate a new initial map for each new closure every time;
  • #1 does not cause a mark-sweeps by overflowing map space (only cheap scavenges).

If we compare #3 and #4 then differences are:

  • #3 does not allocate a new initial map for each new closure every time;
  • #3 does not cause a mark-sweeps by overflowing map space (only cheap scavenges);
  • #4 does less on JS side (no Function.prototype.call, no Object.create, no Object.prototype lookup etc) more on C++ side (#3 also enters runtime every time you do Object.create but does very little there).

Bottom line here is that the first time you use closure as a constructor is expensive compared to subsequent construction calls of the same closure because V8 has to setup some plumbing. If we immediately discard the closure we basically throw away all the work V8 has done to speedup subsequent constructor calls.


The problem is that you can inspect the current source code of various engines, but it won't help you much. Don't try to outsmart the compiler. They'll try to optimize for the most common usage anyway. I don't think (function() { return this; }).call(Object.create(Object.prototype)) called 1,000 times has a real use-case at all.

"Programs should be written for people to read, and only incidentally for machines to execute."

Abelson & Sussman, SICP, preface to the first edition


I guess the following expansions explain what is going on in V8:

  1. t(exp1) : t(Object Creation)
  2. t(exp2) : t(Object Creation by Object.create())
  3. t(exp3) : t(Object Creation by Object.create()) + t(Function Object Creation)
  4. t(exp4) : t(Object Creation) + t(Function Object Creation) + t(Class Object Creation)[In Chrome]

    • For hidden Classes in Chrome look at : http://code.google.com/apis/v8/design.html.
    • When a new Object is created by Object.create no new Class object has to be created. There is already one which is used for Object literals and no need for a new Class.


Well, those two calls don't do exactly the same thing. Consider this case:

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

So we can see that calling Rock.call(otherRock) does not cause otherRock to inherit from the prototype. This has to account for at least some of the added slowness. Although in my tests, the new construct is almost 30 times slower, even in this simple example.


new f;
  1. take local function 'f' (access by index in local frame) - cheap.
  2. execute bytecode BC_NEW_OBJECT (or something like that) - cheap.
  3. Execute the function - cheap here.

Now this:

g.call(Object.create(Object.prototype));
  1. Find global var Object - cheap?
  2. Find property prototype in Object - so-so
  3. Find property create in Object - so-so
  4. Find local var g; - cheap
  5. Find property call - so-so
  6. Invoke create function - so-so
  7. Invoke call function - so-so

And this:

new (function() { })
  1. create new function object (that anonymous function) - relatively expensive.
  2. execute bytecode BC_NEW_OBJECT - cheap
  3. Execute the function - cheap here.

As you see case #1 is least consuming.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜