V8 Engine Voodoo: Why is this faster / slower?
I am currently working on an image editor and stumbled over this weird behaviour regarding pixel manipulation and/or function calls in V8.
http://jsperf.com/canvas-pixelwise-manipulation-performance
There are two test cases. Both test cases should manipulate the image data of an in-memory canvas to increase the brightness. So they have to iterate over every pixel and manipulate the 4 color values of each pixel.
Case 1
Case 1 does "1 function call in total" which means that it passes the context and the imageData to a function which then iterates over the pixels and manipulates the data. All in one function
Case 2
Case 2 does "1 function call per pixel " which means that it iterates over the pixels and calls a method for every pixel, which then manipulates the imageData for the given pixel. This results in (in this case) 250000 additional function calls.
My expectation
I would expect that case 1 is a lot more faster than case 2 since case 2 is doing 250000 additional function calls.
The result
In Chrome, it's exactly the other way around. If I do 250000 additional function calls, it's faster than one single function call handling all image manipulations.
My question: WHY?
Neither code manipulates any canvas and defining a function inside the benchmark loop doesn't really make sense. What you want is static functions that are not re-created ever so that once the JIT has optimized them, they stay optimized. You don't want to measure the creation of a function overhead because a real application would only define the function once.
Once you fix the benchmark code, they should run at equal speed because the manipulatePixel
function will get inlined.
http://jsperf.com/canvas-pixelwise-manipulation-performance/4
I have also created another jsperf where I purposefully manipulate V8 heuristics* not to inline the manipulatePixel
function:
http://jsperf.com/canvas-pixelwise-manipulation-performance/5
As you can see, it's now 50% slower. The only difference between the 2 jsperfs is the huge comment in the manipulatePixel
function.
*V8 looks at the raw textual size of the function (including comments) as a heuristic in inlining decision .
I'm not all too familiar with V8's optimalization wizardry, but I'd say that case 2 leaves more room for the V8 engine to rewrite the code.
Although, at first glance, case 1 should perform better, but it doesn't leave much room for V8 to work its magic.
Though there is only 1 function, a call object is created, within that function object's scope, a couple of variables are declared and a huge object is being processed.
The second case, though, might just be transformed into a loop, or even byte-shifts, thus eliminating the need for function objects and scopes.
In addition to the scope/function being omitted, your variables (arguments) needn't be copied, so there's no pesky object references left to cause any overhead.
In addition to variables being copied, and references, there's also scope-scanning to consider: Math.abs
called from within a function is (marginally) slower than it is in the global scope. I don't know if this is true or not, but I have this sneaky suspicion that masking variables that were declared in a higher scope might impact performance, too.
You're also using width
and height
in the one-function-approach, which look to me as though they are implied globals. This causes a scope-scan on every iteration of the loops, which will probably cause more drag than those arguments and Math.*
calls...