V8 Optimization Killers
github.com
github.com
Please don't memorize this list. It's just for one JS engine among several, all of which are changing rapidly. For example Apple just shipped FTL, an LLVM-based JIT for Safari (so any list for Safari would have just been largely obsoleted).
If you are writing code for nodejs, none of the other engines are relevant. If the overwhelming majority of your users are using chrome, then v8-specific optimizations aren't a bad idea.
On the issue of the list, most of the guidelines here apply here and in every other engine. For example, I doubt a future JS engine can optimize `arguments` abuse while preserving ES5 semantics.
Currently not optimizable:
Generator functions
Functions that contain a for-of statement
Functions that contain a try-catch statement
Functions that contain a try-finally statement
Functions that contain a compound let assignment
Functions that contain a compound const assignment
Functions that contain object literals that contain __proto__, or get or set declarations.
Likely never optimizable:
Functions that contain a debugger statement
Functions that call literally eval()
Functions that contain a with statement
Which I now realize is sort of what I'm looking for.SpiderMonkey now optimizes a bunch of difficult cases it never optimized before; some of them seemed like they would never get optimized, but now they're fine. V8 does some nice optimizations now that weren't possible before, similarly.
var x = 5; something.propertyOnSomething = 5 with(something) { x += propertyOnSomething } alert(x); // What is the value of x?
eval() causes problems as it can introduce new variables into the local scope making it logically difficult to reason about program behaviour subsequent to the eval(), e.g.
function f(x) { eval(something); return Math.sqrt(x); }
What is the result of f(9)?
It is possible to set things up to make these not catastrophically harm performance, but the question is whether you would rather the engineers working on these engines make uncommon (and arguably bad) things common, or make normal and frequent code fast. Any time spent working on making uncommon things fast is time not spent making other things fast.
function doesntLeakArguments() {
var args = new Array(arguments.length);
for(var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
return args;
}
becomes: function doesntLeakArguments() {
var len = arguments.length;
var args = new Array(len);
for(var i = 0; i < len; ++i) {
args[i] = arguments[i];
}
return args;
}
And also, if you've got a switch statement with more than 128 cases, you've probably got bigger problems on your hands than v8 optimizations.I see some things in here that I can start switching to immediately for some of my node modules.
In the Javascript world, "Your Mileage May Vary" truly is a motto.
Using a hashtable for instruction dispatch in an emulator would be utterly insane. You'd use an array (but the compiler can do a better job by generating raw asm for a jump table.)
People overestimate the performance of hash tables. It's true that they are quite fast on a modern CPU, so you get away with using them extensively in dynamic languages like JS, but they are still way way slower than a jump table or directly accessing fields at statically known offsets.
It looks like you're right, very right unless there's something else going on in the perf tests.
1) It shouldn't exercise the GC. GC pressure totally skews jsperf results in a way it can't compensate for.
2) It should compute a result on every iteration that is checked at the end to make sure it is accurate. (This ensures that the compiler can't optimize out unused results from your benchmark loop)
3) The check should have actual control flow so it can't be ignored.
It's also worthwhile to warm all your test cases once in the setup portion of the page before the loop runs. Some performance issues only appear once you've passed multiple argument types into a function, so if you run a test case that passes in ints, and then run a test case that passes in floats, the second test case will be slower for no obvious reason. You can swap the test cases around and the second run will still be slower.
(There are actually a huge list of problems with javascript microbenchmarks, and jsperf only adds to them by being low quality software. Please use it sparingly.)
for(var i=0;i<a.length;i++) if(check(a[i])) a.push('foo');
Unless you can specifically measure caching the length is faster, I wouldn't do it. Most of the time it doesn't matter anyway (won't cause a bump in performance), and even then most of the time the compiler will be able to figure it out for you.
function adder(arr, obj) {
for (var i = 0; i < arr.length; ++i) {
obj.x += i;
}
}
var arr = [1];
var obj = Object.defineProperties({}, {
x: {
get: function() { return 1; },
set: function(v) { arr.push(v); }
}
};
adder(arr, obj);- where are you going to 'mark the array'? Does that mean every array gets the overhead of having room for that mark or do these marks live elsewhere? If so, where, and how is code checking it going to be performant?
- in nested calls, are you going to allow multiple marks? (Solvable, I think, as you can get away with an integer mark count)
- that would mean that all code that modifies any array must check whether such a mark is present and then find the return address on the stack (and nested return addresses)
- even if you do all of the above, the simple checking for presence of such a mark can kill performance in tight loops, even if you do that with branch prediction hints.
- how do you know what mutating changes are important for the calling code? You can use a function pointer as the 'mark' and call that so that it can figure it out by itself, but that surely will kill performance in tight loops.
I think the normal way is for compiled functions to detect when their own assumptions (may) no longer hold, not for other code to signal any compiled code on the stack that its assumptions (may) no longer hold. In tight frequently run code, you may be able to inline calls and recompile, but that is costly, too.
EDIT: I just realize that it is possible to do something in the spirit of this without needing any marks. The really bad calls, such as those to eval, will have to tell the JIT compiler that the current game is over, and that they can start over. It is too costly to do that from any innocuous call, such as that which adds an item to an array, though.
As to that length optimization: it use to say that C doesn't have a for loop; because it evaluates the end condition through every iteration, its 'for' is a 'while' in disguise. Pascal, on the other hand, has a for loop.
JavaScript inherits that IMO bad decision.
Is it probable that SpiderMonkey and other engines suffer from forms of this deoptimization hell when rendering? And would it be a safe guess that whatever mod_pagespeed does to a page's javascript resources is less likely to result in this hell than these other tools? Thanks.
I'm not sure about SpiderMonkey, but they may very well use this kind of optimization too. However, I would guess that Uglifying files should not increase execution speed for any noticeable amount.
here says: http://www.2ality.com/2013/04/check-undefined.html void 0 is safe.
undefined = 'lol';As you can imagine, this didn't actually affect anyone.
http://jsperf.com/check-for-undefined-argument
On Chrome 35, looks like comparing to void 0 is a bit faster. Would love some more samples though.
var keys = Object.keys(obj),
length = keys.length,
key, i;
for (i = 0; i < length; i++) {
key = keys[i];
}
While this is not exactly the same as for..in, it usually behaves how you'd expect and is significantly faster for a couple of reasons:1. In a for..in loop the engine must keep track of the keys already iterated over, whereas in the fast version we can simply increment a counter and do a fast array lookup.
2. It's possible to add properties to the object that you're iterating within the body of the for..in statement, and these will be iterated too. Doing such a thing is obviously very rare but it's the kind of edge case the JS engine must keep track of.
Object.keys(obj).forEach(function (key) {
console.log(obj[key]);
});
but does that hamstring my performance?But note that in your specific case chances are the cost of a bunch of console.log() calls completely swamps the cost of either a for loop or a forEach call. console.log() has _incredibly_ complicated behavior.
Deleting properties is guaranteed to not break iteration and deleting an as-of-yet unvisited property causes it to never be visited.
The performance differs only with 50%, I expected more actually. Am I doing something wrong?
So expected a quite more than a factor of 2 ...
Maybe apart from games, most of this is irrelevant for browser side javascript.
Also nice that IE doesn't still require prefixes for established things like transition/transform. If only Chrome and others would get around to implementing other useful CSS3 features that IE has (like grid) we might be able to actually use them some day.
The "not something a human should ever generate" doesn't seem like a right statement : before a transpiler can generate this kind of code, a human has to understand it.
It's like complaining if a C++ performance article only tested with GCC. Sure, there's other compilers out there, but that's the big one.
- manipulating `arguments` should be avoided
- when possible, avoid looping over the properties of an object (using `in` or `Object.keys`)
In the process of optimizing for v8, you may expose other bad patterns (e.g. megamorphic functions) whose replacement will boost performance everywhere.
For example, even though https://github.com/petkaantonov/deque was developed and tuned for v8, it is still much faster than alternatives in other engines.
http://jsfiddle.net/G83mW/14/ has a testcase for the try/catch thing that is interesting to compare in different browsers...