****Clarification**: I'm not looking for the fastest code or optimization. I would like to understand why some code that seem to not be optimized or optimal run in fact in general consistently faster.
Why is this code:
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
More performant than this one?
var index = Math.floor(ref_index) * 4;
This week, the author of Impact js published an article about some rendering issue:
http://www.phoboslab.org/log/2012/09/drawing-pixels-is-hard
In the article there was the source of a function to scale an image by accessing pixels in the canvas. I wanted to suggest some traditional ways to optimize this kind of code so that the scaling would be shorter at loading time. But after testing it my result was most of the time worst that the original function.
Guessing this was the JavaScript engine that was doing some smart optimization I tried to understand a bit more what was going on so I did a bunch of test. But my results are quite confusing and I would need some help to understand what's going on.
I have a test page here:
http://www.mx981.com/stuff/resize_bench/test.html
jsPerf: http://jsperf.com/local-variable-due-to-the-scope-lookup
To start the test, click the picture and the results will appear in the console.
There are three different versions:
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
var indexScaled = (y * widthScaled + x) * 4;
scaledPixels.data[ indexScaled ] = origPixels.data[ index ];
scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];
scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];
scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = Math.floor(ref_index) * 4;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];
ref_index+= ref_step;
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
var ref_index = 0;
var ref_indexScaled = 0
var ref_step = 1 / scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+1 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+2 ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ index+3 ];
ref_index+= ref_step;
}
}
jsPerf: http://jsperf.com/so-accessing-local-variable-doesn-t-improve-performance
The only difference in the two last one is the calculation of the 'index' variable. And to my surprise the optimized version is slower in most browsers (except opera).
Opera
Original: 8668ms
Optimized: 932ms
Hybrid: 8696ms
Chrome
Original: 139ms
Optimized: 145ms
Hybrid: 136ms
Safari
Original: 433ms
Optimized: 853ms
Hybrid: 451ms
Firefox
Original: 343ms
Optimized: 422ms
Hybrid: 350ms
After digging around, it seems an usual good practice is to access mainly local variable due to the scope lookup. Because The optimized version only call one local variable it should be faster that the Hybrid code which call multiple variable and object in addition to the various operation involved.
So why the "optimized" version is slower?
I thought that it might be because some JavaScript engine don't optimize the Optimized version because it is not hot enough but after using --trace-opt
in chrome, it seems all version are properly compiled by V8.
At this point I am a bit clueless and wonder if somebody would know what is going on?
I did also some more test cases in this page:
http://www.mx981.com/stuff/resize_bench/index.html
The "Value not used" is just a warning; it tells you that your variable p only exists within the try-block. You can declare your p-variable before the try - that way, you can use it outside the try-scope(the scope of a variable refers to where it exists, in this case, only inside the try-block).
Local variables are useful when you only need that data within a particular expression. For example, if you need to reuse a calculated value in multiple places within a single expression, you can store that in a local variable.
If so, local variable ... value not used means that you are storing a value ( input(...) ) in a variable ( user_answer ) and then you're never using that value. But that is just as it should be, because it seems your program ends there; nothing is using the new value of user_answer .
So, by using a local variable you decrease the dependencies between your components, i.e. you decrease the complexity of your code. You should only use global variable when you really need to share data, but each variable should always be visible in the smallest scope possible.
As silly as it sounds, the Math.whatever()
calls might be tricky to optimize and inline for the JS engines. Whenever possible, prefer an arithmetic operation (not a function call) to achieve the same result.
Adding the following 4th test to http://www.mx981.com/stuff/resize_bench/test.html
// Test 4
console.log('- p01 -');
start = new Date().getTime();
for (i=0; i<nbloop; i++) {
var index = 0;
var ref_indexScaled = 0
var ref_step=1/scale;
for( var y = 0; y < heightScaled; y++ ) {
for( var x = 0; x < widthScaled; x++ ) {
var z= index<<2;
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
scaledPixels.data[ ref_indexScaled++ ] = origPixels.data[ z++ ];
index+= ref_step;
}
}
}
end = new Date().getTime();
console.log((end-start)+'ms');
Yields the following numbers in Opera Next:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With