Loop optimization issues in node.js and chrome? - javascript

I happened to see the saying that avoiding to read the length attribute of an array at every iteration saves execution time.
But I don't think it matters and made an experiment.
Then I met the questions.
Here's the code.
// Function to get the execution time.
function testFunction (func) {
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
// Init the array to iterate.
let arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
function loopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function loopWithoutSavedLength() {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
testFunction(loopWithoutSavedLength)
testFunction(loopWithSavedLength)
And the output is very strange:
loopWithoutSavedLength: 889.633ms
loopWithSavedLength: 1023.269ms
I tried many times under Node.js 9.8.0(with v8 6.2.414.46-node.21) and the loopWithoutSavedLength execution time is always shorter than loopWithSavedLength's.
I execute the same script in chrome 66.0.3359.181(with v8 6.6.346.32) console and they are barely the same.
loopWithoutSavedLength: 1475.060302734375ms
loopWithSavedLength: 1493.14892578125ms
Then I thought it might be issue with assignment and test empty loop.
Here is new code.
function assignmentLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {
arr[i] = i
}
}
function assignmentLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {
arr[i] = i
}
}
function emptyLoopWithSavedLength () {
let len = arr.length
for (let i = 0; i < len; i++) {}
}
function emptyLoopWithoutSavedLength () {
for (let i = 0; i < arr.length; i++) {}
}
testFunction(emptyLoopWithSavedLength)
testFunction(emptyLoopWithoutSavedLength)
testFunction(assignmentLoopWithSavedLength)
testFunction(assignmentLoopWithoutSavedLength)
The results in node.js:
emptyLoopWithSavedLength: 580.978ms
emptyLoopWithoutSavedLength: 584.923ms
assignmentLoopWithSavedLength: 1046.899ms
assignmentLoopWithoutSavedLength: 901.542ms
The results in chrome console:
emptyLoopWithSavedLength: 584.126953125ms
emptyLoopWithoutSavedLength: 892.776123046875ms
assignmentLoopWithSavedLength: 1455.418212890625ms
assignmentLoopWithoutSavedLength: 1449.7529296875ms
Later I realized the value stored in arr may influence the consequence and it does.
New code here:
let arr = []
function initArr () {
arr = []
for (let i = 0; i < 10000000; i++) {
arr.push(0)
}
}
function testFunction (func) {
initArr()
console.time(func.name)
for (let i = 0; i < 100; i++) {
func()
}
console.timeEnd(func.name)
}
The result in node.js:
emptyLoopWithSavedLength: 560.739ms
emptyLoopWithoutSavedLength: 1134.274ms
assignmentLoopWithSavedLength: 1841.544ms
assignmentLoopWithoutSavedLength: 1609.649ms
The result in chrome console:
emptyLoopWithSavedLength: 592.8720703125ms
emptyLoopWithoutSavedLength: 910.886962890625ms
assignmentLoopWithSavedLength: 1457.467041015625ms
assignmentLoopWithoutSavedLength: 1488.855224609375ms
Now new question comes up.
In conclusion:
1. In node.js, why is loopWithoutSavedLength time always smaller than loopWithSavedLength no matter array's elements are initialized to 0 or not?
2. Why does creating new array and initializing its elements to 0 make a different consequence?

V8 developer here.
Question 1: loopWithoutSavedLength is faster because accessing an element needs to perform a bounds check, which needs to load the length anyway. It can be eliminated if the loop's condition already contains the same check. It is much harder for the compiler to eliminate the additional check if you save the length. So in effect, saving the length manually means that work is duplicated. That said, the difference is usually too small to matter (or even be measurable). See the excellent writeup at https://mrale.ph/blog/2014/12/24/array-length-caching.html for more details.
Question 2: I'm not sure. The values of the elements shouldn't matter; and in Chrome they don't. V8 in Node.js acts the same and should perform the same as in Chrome; but maybe there's a difference between the two V8 versions you've tested. I'm confused by the results you got for emptyLoopWithoutSavedLength in Node.js, which apparently went from 584 to 1134 for no good reason at all -- maybe something else in your system caused a temporary slowdown? Can you reproduce this result?
Speaking of reproducing: when I run these tests repeatedly, I see performance differences of about 10% between repeated runs of the very same test (e.g. the results I got for assignmentLoopWithSavedLength over the course of 10 runs were between 1075 and 1222). That's not unusual; modern computers are complex machines with many layers and many sources of performance variations. It just means that when you do a single run of two tests, and you see 1400 and 1450 milliseconds, that might not mean anything -- next time around they might swap results. If you see 1455 and 1449, the difference is almost certainly noise.

Related

Is there a limit to how many levels you can nest in JavaScript?

Say you have this really complex algorithm that requires dozens of for loops.
Does JavaScript have a limit to how deep loops can be nested or is there no limit?
What is the best practice for deep nested for loops?
I tried searching on MDN but couldn't find what I was looking for
Edit
I'm looking if there is a built in limit. For example if you had something like this
If ( a = 1, a < 3, a++) {
if (b = 1; b < 3; b++) {
...
if (cd = 1; cd < 3; cd++)
Would this actually be possible or would JS throw an error
Edit: Here's a theoretical example of when you might need this
You want to find if any 500 numbers in an array sum up to equal another number. You'd need about 500 loops to add the numbers to a combos array and then filter them to find the sum of them relative to a third number.
Would there even be enough space in the universe to store that much data?
There's no limit in the specification. There will probably be a limit in any implementation due to memory/stack overflows...
For example, this works fine:
var s = 0;
var is = new Array(11);
for(is[0] = 0; is[0] < 2; is[0]++) {
for(is[1] = 0; is[1] < 2; is[1]++) {
for(is[2] = 0; is[2] < 2; is[2]++) {
for(is[3] = 0; is[3] < 2; is[3]++) {
for(is[4] = 0; is[4] < 2; is[4]++) {
for(is[5] = 0; is[5] < 2; is[5]++) {
for(is[6] = 0; is[6] < 2; is[6]++) {
for(is[7] = 0; is[7] < 2; is[7]++) {
for(is[8] = 0; is[8] < 2; is[8]++) {
for(is[9] = 0; is[9] < 2; is[9]++) {
for(is[10] = 0; is[10] < 2; is[10]++) {
s++;
}
}
}
}
}
}
}
}
}
}
}
document.write(s);
Just to kick in with a short test that I've since modified to not count loops as much as see the memory limit... This code (please don't use it, your machine will hate you):
function x () {
function newLoop (index) {
var y = [];
console.log("index");
for (i = index; i < index+1000; i++) {
y.push(i);
if(i == index+999) {
console.log(i);
newLoop(i);
}
}
}
newLoop(0);
}
x();
has stopped at logging 499500 to the console. That is probably hitting some safety switch or the memory limit.
That's 500 nested loops.
In an earlier test with a lighter version of this code I've gotten up to 999 nested loops in just the first second, with the code clogging up my browser for another few seconds (but not displaying the rest because of "too many messages per second to the console" error).
I didn't care enough too bother with more details after that nor do I see benefits of a more detailed description here, but (in my project) I'm traversing HTML with a whole lot of nested loops in badly laid out pages and these results faaar exceed my needs.
TL;DR: memory plays a bigger role than the number of loops, but I've gotten above a 1000 nested loops. Please don't ever use that many though :)
PS. this was run in Edge, for the version check the date of my post :)
There is no maximum nesting level that you should worry about when you are writing code that a human is supposed to read and maintain. You can nest hundreds of loops without problems.
However, you should avoid it wherever possible! At some point someone will have to understand your code (most likely it's you, when you are debugging!) and will curse you. It should be possible to extract inner loops into a separate function with a meaningful name.

When updating some of the elements in an object array in Javascript, which one is a more efficient way considering the array size?

Consider this very simple js code below:
for(var i = 0; i < rows.length; i++) {
if(rows[i].index !== i) {
rows[i].index = i;
}
}
Say, the length of the array is 8, and it will enter the if block 2 times. Is it better to do this way:
for(var i = 0; i < rows.length; i++) {
rows[i].index = i;
}
I want to know which one is less costly with large arrays and small arrays; the if block, or the value assign in every cycle of the loop?
it shouldn't really matter. I still tried it on jsPerf for the sake of curiousity and it seems that the second version is faster.

Why does CoffeeScript compile to unidiomatic JavaScript?

I'm playing around with Coffee script (I'm new to this "language") and tried just a very basic example:
x = [1, 2, 3]
for element in x
console.log(element)
This does what it says, for each of the elements it outputs it to the console. It's only when I took a lot at the Javascript it compiled to that I can't understand why they do this:
(function() {
var element, i, len, x;
x = [1, 2, 3];
for (i = 0, len = x.length; i < len; i++) {
element = x[i];
console.log(x[i]);
}
}).call(this);
Would it not just be better and more natural to do:
for(i = 0; i < x.length; i++)
{
console.log(x[i]);
}
Why does coffee script compile to such a way that does not seem natural? If I was writing the same exact code in Javascript, I would follow the method that I did, but I might be wrong in that the way I did it (Javascript) is not the most efficient or "natural" and the coffee script method is a better solutions.
Basically you are expecting the CoffeeScript compiler to do acrobatics that would be really complicated.
Consider this CoffeeScript:
for a in x
console.log(a)
for b in a
console.log(b)
callFunc(b)
This transpiles to:
for (i = 0, len = x.length; i < len; i++) {
a = x[i];
console.log(a);
for (j = 0, len1 = a.length; j < len1; j++) {
b = a[j];
console.log(b);
callFunc(b);
}
}
It's very easy to see how the original CoffeeScript corresponds to the resulting JavaScript:
for [variable] in [array]
[body]
becomes
for (var [letter] = 0, len = [array].length, [letter] < len; [letter]++) {
[variable] = [array][[letter]]
[body converted to JavaScript]
}
So when translating a for comprehension into JavaScript, (almost) all the compiler needs to worry about is this simple correspondence, and then it can work inward and process the body as a separate and independent operation.
Also, the len = [array].length caching is done for performance. This isn't entirely pleasant to look at, and most people wouldn't code this way, but producing beautiful JavaScript is not the primary goal of CoffeeScript. It produces efficient code with the assumption that people won't generally be reading the generated JavaScript.
What you are suggesting is that it take that original code and turn it into:
for (i = 0; i < x.length; i++) {
console.log(x[i]);
for (j = 0; j < x[i].length; j++) {
console.log(x[i][j]);
callFunc(x[i][j]);
}
}
In order to convert console.log(b) into console.log(b);, the compiler doesn't need to know anything about the code surrounding that statement. In order to convert console.log(b) into console.log(x[i][j]);, the compiler would have to take everything around that statement into account. It would give the compiler an extremely complicated job to perform and wouldn't really provide any benefit.
Firstly, CoffeeScript builds everything in a closure by default, that would be these lines:
(function() {
}).call(this);
Closures prevent variables leaking out of the script. If you want to export your script as a global, then you need to explicitly attach it go a global, like window.
Next is the line:
var element, i, len, x;
It is considered good practice to declare your var's before using them (at least according to jslint).
Your definition of x of course:
x = [1, 2, 3];
Now the loop:
for (i = 0, len = x.length; i < len; i++) {
element = x[i];
console.log(x[i]);
}
Defining len first prevents the loop constantly looking up what x.length is. Not a huge speed difference with only three items, but an array with 10k, well...
The console.log is obvious, but the element variable is defined because that is the name of the variable you specified when you wrote your loop. And it uses i instead of element to iterate mostly because it's standard practice to use i for iteration.
So you see everything has a legitimate reason, albeit a little verbose for some tastes.

What is the most idiomatic way to handle variables declared in multiple for loops? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
JavaScript only has function scope. Thus, variables declared in for loops are visible for the entire function.
For example,
function foo() {
for(var i = 0; i < n; i++) {
// Do something
}
// i is still in scope here
}
When we have multiple for-loops, this opens the question of how we handle the variables in these other for loops.
Do we use a different variable?
for(var i = 0; i < n; i++) { }
for(var j = 0; j < n; j++) { }
Or do we use the same variable but just assign a value (instead of declaring it)?
for(var i = 0; i < n; i++) { }
for(i = 0; i < n; i++) { }
Or declare i outside of the loops?
var i;
for(i = 0; i < n; i++) { }
for(i = 0; i < n; i++) { }
Or redeclare i?
for(var i = 0; i < n; i++) { }
for(var i = 0; i < n; i++) { }
All of these work (or at least they do on the latest versions of my browsers). JSHint doesn't like the last approach, though.
Is there an approach which is most idiomatic or otherwise preferable?
It really depends on who you're coding for. If you're coding for a company or contributing to a library you of course follow their style guide. I've seen all of these (expect the last) used in libraries. If you like the Douglas Crockford style you'll go with the second to last and place all your variables at the top of function scope (or jslint will shout at you).
Taking an example from the jQuery style guide:
This is considered Good style
var i = 0;
if ( condition ) {
doSomething();
}
while ( !condition ) {
iterating++;
}
for ( ; i < 100; i++ ) {
object[ array[ i ] ] = someFn( i );
}
While this is poor style:
// Bad
if(condition) doSomething();
while(!condition) iterating++;
for(var i=0;i<100;i++) object[array[i]] = someFn(i);
Anyway, because this is style I'm going to reference how several libraries write their for each loops:
jQuery uses the Crockford style in core as does lodash
Underscore js uses the last style as seen here. Mootools also uses this style
If your code is going to be minimized before you release it, it will not matter as minifiers will mangle it to pretty much the same end representation in the processing.
Using different variables we have no problems.
Reusing and reassigning makes the code less readable, and if we remove the declaration at a later time, we risk assigning i to something outside of the function scope.
Declaring i outside the loops, we have no problems.
Redeclaring will be an issue if your lint tool, IDE, etc complain.
So I would argue for the first or third option. If number of variables is a concern using the first option, then you are may be in need of a refactoring.
Another take that answers the question in a different way.
A function with multiple loops makes me suspicious because:
It may be doing too much and should be decomposed anyway, and
It may be better to write it more functionally and eliminate the index altogether (it's available in some each-/map-y functions anyway)
Another approach is to use iterator functions. For example, in modern browsers an Array will have a forEach method:
var items = ["one", "two", "three"];
var things = ["hello", "goodbye"];
items.forEach(function (item, index) {
// Do stuff
});
things.forEach(function (item, index) {
// Do stuff
});
If you're using older browsers (or custom collections), you can make your own iterator like this:
Array.prototype.forEach = function(callback) {
for(var i = 0; i < this.length; i++) {
callback.apply(this, [this[i], i, this]);
}
};
For more information see: Array.prototype.forEach()
Any variables declared within a function are interpreted as being declared at the beginning of the function. Doug Crockford argues that you should declare all of your variables at the first line of every function.
doSomething = function() {
var i, ... other variables ...;
...
for (i = 0; i < x; i += 1) {
...
}
...
for (i = 0; i < x; i += 1) {
...
}
}
This way the code reads in the same way it will be parsed by the javascript engine.

Variable definitions in nested for loops?

Some static languages like Java seem to have very special rules for variables defined in the first argument of a for loop. They are accessible only by the given loop, which makes them behave pretty much like javascript functions' local variables and arguments. I mean stuff like this:
class ForVariable {
public static void main(String[] args) {
for(int i = 0; i != 0; i++) {}
System.out.println(i); // Throws an Exception
}
}
Javascript doesn't behave like that, which makes nesting loops quite a messy business. My question is: Is it valid to declare variables in the subsequent loops via the var keyword? In other words - which of the following examples is valid?
for(var i = 0, j; i < 5; i++) {
for(j = 0; j < 10; j++) <do some stuff>;
}
OR
for(var i = 0; i < 5; i++) {
for(var j = 0; j < 10; j++) <do some stuff>;
}
Clearly it is wrong to declare a variable several times, which would make the 2nd example a no-go, but given the fact that the 1st example is the way loops nesting is done in most languages I know, I'm rather hesitant to declare the winner.
Those are both valid. Function scoped vs block scoped. Basically both loops in JavaScript become:
function a () {
var i, j;
for(i = 0, j; i < 5; i++) {
for(j = 0; j < 10; j++) <do some stuff>;
}
}
because the var declarations are hoisted to the top
Its not wrong to declare a variable several times. For instance there is really no problem with:
var i = 0;
var i = 1;
That's valid JavaScript. Good tools like the Closure Compiler will generate a warning though because you typically don't intend to do that.
That being said, even the Closure Compiler won't generate a warning for your example #2. It's just common convention in JS even if you are technically re-declaring.
Either of your two examples is fine but the second one is a little more sensible to parse. I wouldn't worry about it either way.
You don't want to be using the var keyword, but rather function arguments, because javascript is not block-scoped. For example:
[100,200,300].forEach(function (x,i) {
[10,20,30].forEach(function (y,j) {
console.log('loop variables, indices '+[i,j]+' have values: '+[x,y]);
});
})
or
[100,200,300].map(function (x,i) {
return [10,20,30].map(function (y,j) {
return x+y;
});
})
// result: [[110,120,130],[210,220,230],[310,320,330]]

Categories