jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
This tests the cost of calling a SetPixel() function within a rendering loop versus direct array manipulation as well as comparing accessing ImageData against an Array and a Typed-Array. The motivation is to determine efficient javascript rendering for a game engine that performs custom rendering (like JSNes).
The test also compares accessing an ImageArray with Array and Int32Array, where Array (in Chrome) yields the highest performance, though they are all about the same for this experiment.
The results suggest that a fair amount of loop unrolling is still necessary (as of December 2011 on Chrome), but I do expect this area to be improved greatly in the coming years as that can easily be inlined.
One improvement for custom rendering of colours with 1-bit transparency (where alpha values < 255 are considered fully transparent), is to pack them into an ARGB int value. Any alpha value below 255 produces a negative number, which is easier to test than an RGBA value that would require an additional bitwise AND operation. It's a tiny optimization, yes, but worth being aware of for the case of Javascript rendering. Will benchmark this.
Note: Int32Array is used to test typed arrays as an earlier test with Uint8Array was much slower, though it might be worth adding it to the benchmark for comparison sake. Int32Array is used in favour of Uint32Array since it is only concerned with 0-255 range.
Also note:
<canvas id="bg0" width="160" height="144">
</canvas>
var canvas = document.getElementById("bg0");
var context = canvas.getContext("2d");
var imageData = context.createImageData(canvas.width, canvas.height);
const STRIDE_SIZE = canvas.width * 4;
var arrayData = {
width: imageData.width,
height: imageData.height,
data: new Array(imageData.data.length)
};
var s32Data = {
width: imageData.width,
height: imageData.height,
data: new Int32Array(imageData.data.length)
};
function SetPixel(data, ptr, x, y, r, g, b) {
data[ptr] = r;
++ptr;
data[ptr] = g;
++ptr;
data[ptr] = b;
++ptr;
data[ptr] = 0xff;
}
function CreateGradient(imageData, r, g, b) {
var data = imageData.data;
var width = imageData.width;
var height = imageData.height;
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var ptr = (x + y * width) << 2;
var value = ((255 * x / width) + (255 * y / height)) / 2;
var cr = ~~ (value * r);
var cg = ~~ (value * g);
var cb = ~~ (value * b);
data[ptr] = cr;
++ptr;
data[ptr] = cg;
++ptr;
data[ptr] = cb;
++ptr;
data[ptr] = 0xff;
}
}
}
function CreateGradientSetPixel(imageData, r, g, b) {
var data = imageData.data;
var width = imageData.width;
var height = imageData.height;
for (var y = 0; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var ptr = (x + y * width) * 4;
var value = ((255 * x / width) + (255 * y / height)) / 2;
var cr = ~~ (value * r);
var cg = ~~ (value * g);
var cb = ~~ (value * b);
SetPixel(data, ptr, cr, cg, cb);
}
}
}
for (var i = 0; i < imageData.data.length; ++i) {
imageData.data[i] = 0xff;
arrayData.data[i] = 0xff;
s32Data.data[i] = 0xff;
}
Ready to run.
Test | Ops/sec | |
---|---|---|
Direct access (with ImageData) |
| ready |
Direct access (with Array) |
| ready |
Direct access (with Int32Array) |
| ready |
Calling a function (with ImageData) |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.