Live data from Hacker News

V8 Optimization Killers

github.com

71–80 of 88 posts

Re: V8 Optimization Killers

#71
post #68
post #60

Earlier quoted context omitted.

JavaScript is complex enough that pretty much any code inside of the loop could end up doing an array push. Even `console.log(myCompletelyUnrelatedObject.x)` (given an evil property descriptor).

I don't know a whole lot about JIT compilers, but would it be possible to emit code with length optimized, and trap changes to it to fall back to a slow path in a way that wouldn't hurt the fast path? For example, you might mark the array so that when other code mutates it, that code also modifies the return address on the stack to point to some fixup code that puts you onto the slow path.

I don't know much about this, either, but I don't think that is a feasible approach.

- 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.

Re: V8 Optimization Killers

#72
post #44
post #42

Earlier quoted context omitted.

No you can't. You can do something like function(undefined) {}, but the global 'undefined' is non-writable and non-configurable.

That's an ES5 change. With ES3, you can overwrite NaN, Infinity, and undefined.

Only in strict mode I though no ?

Re: V8 Optimization Killers

#73
post #72
post #44

Earlier quoted context omitted.

That's an ES5 change. With ES3, you can overwrite NaN, Infinity, and undefined.

Only in strict mode I though no ?

Generally. Technically, it's a "breaking change".

As you can imagine, this didn't actually affect anyone.

Re: V8 Optimization Killers

#74
post #66
post #62

Earlier quoted context omitted.

> if I memorize this particular list 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).

I actually don't want to memorize any list, unless it's a list of things that are inherently expensive/impossible to optimize in JS, in which case it's worthwhile. I just hate the idea of having to write all my code according to some list-of-the-week. What I want is a list that separates the always-know stuff from the engine-specific-tweaks or might-not-apply-next-year stuff.

That list won't ever be accurate. The only way to know is to re-run your performance tests every time Google and Mozilla ship new releases.

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.

Re: V8 Optimization Killers

#75
post #10

Earlier quoted context omitted.

Huge switches are common in the inner loop of emulators and interpreters. They tend to end up with a 256-case switch to handle the next byte in the instruction stream.

Why would the use a switch instead of a hashtable?

Good compilers optimize a switch into a jump table, which is much, much faster than a hashtable.

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.)

Re: V8 Optimization Killers

#76
post #15

Which is better `undefinedVar === void 0` or `typeof undefinedVar === 'undefined'`? here says: http://www.2ality.com/2013/04/check-undefined.html void 0 is safe.

I was curious too, so here's a jsPerf:

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.

Re: V8 Optimization Killers

#77
post #46

Earlier quoted context omitted.

Try this benchmark out: http://jsperf.com/fastest-array-loops-in-javascript/56 It looks like you're right, very right unless there's something else going on in the perf tests.

There's something going on with the perf tests - most of that code is being optimised away, in one case I got over a billion cycles per second. The trick to these things is writing code which cannot be optimised away, like returning Math.random() from a function and using the return value to increment another variable.

Yes. At present if you want a jsperf test to be remotely accurate:

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.)

Re: V8 Optimization Killers

#78
post #17

My team's developers, who are in a Firefox-only setting (so no V8 to consider), use every optimization/squishing trick they can find on github - grunt, browserify, uglifyjs etc - thinking that the smaller the number of javascript resources and the smaller those files are the faster the pages will not just download but also render. Is it probable that SpiderMonkey and other engines suffer from forms of this deoptimiza…

I'm not aware of any common minification techniques that hurt performance. They generally improve parser performance and reduce memory usage (less memory spent on line-ending data and script sources, for example)

Re: V8 Optimization Killers

#79
post #6
post #2

An excellent overview! A bit worrying that using an object as a hash table and iterating over its keys using ForIn would prevent optimization - I had always thought that this was a common use case that would be well-supported by the optimizer! I suppose in that case, if you need fast reads of all keys and can afford slower writes, you could maintain an array of the keys at insertion time and just loop through that?

`for ... in` is a relatively slow construct anyway, the faster option (a lot more verbose) is: var keys = Object.keys(obj), length = keys.length, key, i; for (i = 0; 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 ca…

Keys is an alloc. That is not 'faster'.

Re: V8 Optimization Killers

#80
post #10

Really great! Some notes that popped out for me are that to always cache the .length property for any array or arguments: function doesntLeakArguments() { var args = new Array(arguments.length); for(var i = 0; i becomes: function doesntLeakArguments() { var len = arguments.length; var args = new Array(len); for(var i = 0; i And also, if you've got a switch statement with more than 128 cases, you've probably got bigge…

Huge switches are common in the inner loop of emulators and interpreters. They tend to end up with a 256-case switch to handle the next byte in the instruction stream.

Would it make sense to use a bitwise test and two 128-part switch blocks?
Post reply on HN