ricklane ricklane - 7 days ago 5
Javascript Question

Why is 2-param drawImage faster than 9-param?

I'm finding that if I am drawing one HTML5 canvas to another, if I use the 2-parameter version of drawImage() where I simply provide the destination x and y coordinates, this is significantly faster than if I use the 9-parameter version, where I specify both the source and destination offsets and bounds. This is true even if the bounds specified in the latter are the same as the size of the source canvas in the former.

I'm seeing this in Chrome.

Here is a jsFiddle demonstrating what I'm seeing:



var cycleResults = document.getElementById('cycleResults');
var result = document.getElementById('result');
var btn = document.getElementById('btn');

var canvas = $('.c')[0],
context = canvas.getContext("2d"),
scratchCanvas = document.createElement('canvas'),
scratchContext = scratchCanvas.getContext("2d"),
scratchCanvas2 = document.createElement('canvas'),
scratchContext2 = scratchCanvas2.getContext("2d");

context.fillStyle = 'red';
context.fillRect(0, 0, 25, 25);
context.fillStyle = 'white';

scratchCanvas.setAttribute('width', 25);
scratchCanvas.setAttribute('height', 25);
scratchCanvas.style.width = '25px';
scratchCanvas.style.height = '25px';
scratchContext.fillStyle = 'white';
scratchContext.fillText('F', 12, 12);

scratchCanvas2.setAttribute('width', 8);
scratchCanvas2.setAttribute('height', 8);
scratchCanvas2.style.width = '8px';
scratchCanvas2.style.height = '8px';
scratchContext2.fillStyle = 'white';
scratchContext2.fillText('F', 0, 8);

function test1()
{
context.drawImage(scratchCanvas2, 12, 4);
}

function test2()
{
context.drawImage(scratchCanvas, 12, 4, 8, 8, 12, 4, 8, 8);
}

function test3()
{
context.fillText('F', 12, 12);
}

// BENCHMARK ====================
btn.onclick = function runTests(){
btn.setAttribute('disable', true);
cycleResults.innerHTML = '';
result.textContent = 'Tests running...';

var suite = new Benchmark.Suite;

// add tests
suite
.add('test1', test1)
.add('test2', test2)
.add('test3', test3)
// add listeners
.on('cycle', function(event) {
var result = document.createElement('li');
result.textContent = String(event.target);

document.getElementById('cycleResults')
.appendChild(result);
})
.on('complete', function() {
result.textContent = 'Fastest is ' + this.filter('fastest').pluck('name');
btn.setAttribute('disable', false);
})
// run async
.run({ 'async': true });
};

<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/1.0.0/benchmark.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas class="c" width=25 height=25></canvas>
<ul id='cycleResults'>

</ul>
<div id="result">

</div>
<br>
<button id="btn">
Run Tests
</button>





In the end, I'm trying to determine if this is a valid optimization in lieu of using fillText(). fillText() is faster than the 9-param drawImage() call, but slower than the 2-param call. Is this expected, and can someone explain why this is the case?

Answer

The 2-param version ultimately calls the same 9-param version, the difference in performance between the two tests is that in the 2-param version, we're drawing the entire source image, whereas on the other we're drawing only a portion of it. The former scenario is more likely to benefit from having all pixels stored in contiguous bytes in RAM which will help prevent L1 cache misses.