fr/en
Dernier postes dans dévelopment

(Désolé mais cet article n'existe qu'en anglais)

Last week I spent some time optimizing a small canvas demo that I've done with some good results (~40% speedup in Google Chrome). During those hacking session I came up with a general hack that can lead to a big increase in performance. So I thought that it was worth looking at it in details.

The problem

HTML 5 canvas element is a formidable tool and for a lot of things it's really fast (just look at chrome experiments or canvas demo for some nice example). However there is one domain where the speed you can achieve is less than satisfactory: per-pixel writing. Let say you want to fill the canvas surface with pixels generated by some calculation, you will have to write every pixel of the canvas with some new values. The resulting code would look like that

canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); var pixels = SCREEN_WIDTH*SCREEN_HEIGHT; while(--pixels){ image.data[4*i+0] = r; // Red value image.data[4*i+1] = g; // Green value image.data[4*i+2] = b; // Blue value image.data[4*i+3] = a; // Alpha value } context.putImageData(image, 0, 0);

For each pixel of the image we have to write 4 values (one for each color and the alpha channel) so if this operations turn out to be slow, this loop will be very inefficient.

The Hack

After listening to a YUI talk about javascript performance insisting on how DOM access was slow and should be avoided I wondered if every write to an element of the data array was a DOM access. This would explain the slowness of it. So I came up with this idea: Why not "detaching" the image's data array to manipulate it and copy it back before rendering. It turned out that it provided a nice speed-up in chrome, so much in fact that I made a simple benchmark to isolate the effect from the big mess of the application I was optimizing.


canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); var pixels = SCREEN_WIDTH*SCREEN_HEIGHT; var imageData = image.data; // here we detach the pixels array from DOM while(--pixels){ imageData[4*i+0] = r; // Red value imageData[4*i+1] = g; // Green value imageData[4*i+2] = b; // Blue value imageData[4*i+3] = a; // Alpha value } image.data = imageData; // And here we attache it back context.putImageData(image, 0, 0);

The advantage of this hack is that it is very easy to apply in any situation of this kind.

The Benchmark

I added to this benchmark a test for something else that I noticed earlier on the development of the same application: going from a globally scoped to a more "Crockfordy" namespaced one may sometime have negative effect on performance. So the benchmark tests for a combination of the following: global vs namespaced methods and standard vs "detached" access to the image's data. To run the benchmark just go there and wait for the four tests to end. They are run one after the other, each in its own frame.

Results

There is more information here as is it seems at first sight. The first thing is that caching the image's data array is always a good idea (red values are always under blues values and green always under oranges values). I can only speculate regarding to why that is... it probably has to do with the cost associated with "crossing the DOM bridge" as describe in the talk I mentioned earlier.

You may wonder "What if instead of just writing the value of each pixel I need to read them first?". Well I did too because intuitively it would seams that this should be an even better candidate for this kind of optimisation. It turned out that this give the exact same result as when you're just writing the value. This would seams to indicate that accessing a value of the data array don't require a "flush" of the pending modification on the page (which make sense since the change to those data are not dirrectly reflected on the page).

The second interesting point is: using namespace has a big impact on performance. This as probably to do with the way modern compilers use tracing to speed things up. It seams that namespacing can make it harder for the compiler to find a valid trace through the code. Indeed, if you look at the second result for Firefox without JIT (tracing disabled) you can see that globally scope function loose their advantage.

There may well be a way to use namespace that doesn't break the tracing but I didn't find any.

Final Word

This is just a simple benchmark. Testing javascript is not a simple task and many factors can come into play. Furthermore those results are of no use comparing performances between browsers. This small article is there to start a discussion and I am open to any comments. Post your results if you use a browser not mentioned here!

As always the code is under a MIT License, so don't hesitate to roll you own version of this benchmark!

tags >> code, javascript permalink >>commentaires >>vue >>ajout

Befunge? Befunge!

Mare de l'éternel troll java contre c++? Alors voici Befunge un langage ésotérique ou la position du program counter est contrôlée en 2D. Des heure d'amusement (et de prise de tête) garantie! Vous pourrez trouver de nombreuse IDE pour vous aider a coder dans ce langage, comme par exemple YABI93. voici un exemple de l'allure que peux avoir un code source en Befunge:

<@_v#!>#:<"Jhw#d#olih$" 3 - > ,^
tags >> code, langage permalink >>commentaires >>vue >>ajout

Autocritique

Peut-être que tous ces procès anti-monopole finissent par porter leurs fruits finalement:

tags >> bug permalink >>commentaires >>vue >>ajout


suivants >>
liste des tags