Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing local variable doesn't improve performance

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

The short version

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;

The long version

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:

The original code:

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

One of my attempt to optimize it:

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

The same optimized code but with recalculating the index variable each time (Hybrid)

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

Results of personal testing (not the jsPerf tests):

  • 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

like image 471
NicMagnier Avatar asked Sep 16 '12 12:09

NicMagnier


People also ask

Why the value of local variable is not used?

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

What is a reason for using local variables in a query?

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.

Why is my local variable not being used Python?

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 .

Why is it good to practice local variables?

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.


1 Answers

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:

  • Original - 2311ms
  • refactor - 112ms
  • hybrid - 2371ms
  • p01 - 112ms
like image 113
Mathieu 'p01' Henri Avatar answered Oct 27 '22 01:10

Mathieu 'p01' Henri