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