Javascript performance in rendering

Atheus

Diamond Member
Jun 7, 2005
7,313
2
0
Here's a little expermiment I'm doing with javascript rendering:

http://www.nicetechnology.co.uk/animate.html

IE7 will spit this out so use Firefox. I'd like to know why IE does this but that's not why I posted. I just want to get it working in FF at the moment.

You'll notice it takes about one second to load and render the random squares. The number of columns of squares is set by the 'cols' variable at the top of the script - try saving the page and changing this variable. If you make it 50 it'll take several more seconds to load. If you make it a few hundred it'll crash your FF.

So why exactly is this so slow? I am creating a lot of objects but they are extremely simple, and surely the DOM of a complex page includes many hundreds of objects anyway, created as the page loads... right?

/edit: Latest:

http://www.nicetechnology.co.uk/animate3.html
 

Ipreferspam

Member
Apr 12, 2008
43
0
0
Do not modify div.innerHTML each time you need to add a square.. Add another variable to the Canvas function that handles all the string concatenation -- this is what you should update or clear in the drawImage and clear methods of Canvas. Then add another function to Canvas, say displayImage(), that takes no parameters. Just call this function on each canvas after the code runs that calls drawImage for the appropriate number of squares. This should drastically speed up the generation.
 

Ipreferspam

Member
Apr 12, 2008
43
0
0
<!--

var cols=50;

// Canvas object
function Canvas(div) {
this.div=div;
this.temp = '';
this.drawImage = function(imgSrc, x, y, w, h, a) {
this.temp += '<div style="position:absolute;'+
'left:' + x + 'px;'+
'top:' + y + 'px;'+
'width:' + w + ';'+
'height:' + h + ';">'+
'<img src="' + imgSrc + '" width="' + w + '" height="' + h + '"' + (a? (' '+a) : '') + '>'+
'<\/div>';
};
this.displayImage = function() {
this.div.innerHTML = this.temp;
};
this.clear = function() {
this.temp = '';
};
}

// Square object
function Square(x,y) {
this.x = x;
this.y = y;
this.width=10;
this.height=10;
this.angle=0;
this.imgurl="square.jpg";
this.render = function(canvas) {
//alert("rendering...");
canvas.drawImage(this.imgurl,this.x,this.y,this.width,this.height);
};
}

// Instantiate squares
var squares = new Array();
var num=0;
var h=0;
var w=1;
var s=screen.width/11;
for(var l=0; l<cols; l++) {
var n=(Math.random()*10)+1;
w+=11;
h=0;
for(var k=0; k<n; k++) {
squares
  1. = new Square(w,h);
    h+=11;
    num++;
    }
    }

    // Implement double buffering using two canvases
    var canvases = [new Canvas(document.getElementById("canvas_1")), new Canvas(document.getElementById("canvas_2"))];

    var i = 0;

    function animate() {
    // Repaint background div
    canvases.clear();
    for(var j=0; j<squares.length; j++) {
    squares[j].render(canvases);
    }
    canvases.displayImage();
    // Swap divs
    canvases.div.style.zIndex = 1;
    i ^= 1;
    canvases.div.style.zIndex = 0;

    }

    animate();
    //var timer = setInterval(animate,50);

    //-->
    </script>
 

Ipreferspam

Member
Apr 12, 2008
43
0
0
And go figure, the only thing I had to chance to get this to work in IE7 was to change your divs to the following:
<div id="canvas_1" class="canvas_1"></div>
<div id="canvas_2" class="canvas_2"></div>
 

Atheus

Diamond Member
Jun 7, 2005
7,313
2
0
Thank you! It still needs imporovement but it's *much* faster now. I'm not a javascript expert as you can tell, but yesterday I had visions of smooth animation in websites without flash, so I thought I'd give it a go :)

Unfortunately I'm still getting 'operation aborted' in IE7 even with the div ending tags. I'll do a search and see if this a common problem but I'm not going to worry about it yet.
 

Ipreferspam

Member
Apr 12, 2008
43
0
0
This dances for me in FF3, IE7, and Google Chrome:

<div id="canvas_1" class="canvas_1"></div>
<div id="canvas_2" class="canvas_2"></div>

<script type="text/javascript">
<!--

var cols=50;

// Canvas object
function Canvas(div) {
this.div=div;
this.temp = '';
this.drawImage = function(src) {
this.temp += src;
};
this.displayImage = function() {
this.div.innerHTML = this.temp;
};
this.clear = function() {
this.temp = '';
};
}

// Square object
function Square(x,y) {
this.x = x;
this.y = y;
this.width=10;
this.height=10;
this.angle=0;
this.imgurl="square.jpg";
this.src = '<div style="position:absolute;'+
'left:' + this.x + 'px;'+
'top:' + this.y + 'px;'+
'width:' + this.width + ';'+
'height:' + this.height + ';">'+
'<img src="' + this.imgurl + '" width="' + this.width + '" height="' + this.height + '">'+
'<\/div>';
this.render = function(canvas) {
canvas.drawImage(this.src);
};
}

// Instantiate squares
var squares = new Array();

// Implement double buffering using two canvases
var canvases = [new Canvas(document.getElementById("canvas_1")), new Canvas(document.getElementById("canvas_2"))];

var i = 0;

function animate() {
// Repaint background div
buildSquares(canvases);
canvases.displayImage();

// Swap divs
canvases.div.style.zIndex = 1;
i ^= 1;
canvases.div.style.zIndex = 0;

}

function buildSquares(canvas)
{
canvas.clear();
squares = new Array();
var num=0;
var h=0;
var w=1;
var s=screen.width/11;
for(var l=0; l<cols; l++) {
var n=(Math.random()*10)+1;
w+=11;
h=0;
for(var k=0; k<n; k++) {
squares
  1. = new Square(w,h);
    squares
    1. .render(canvas);
      h+=11;
      num++;
      }
      }
      }

      animate();
      var timer = setInterval("animate()",50);

      //-->
      </script>
 

Atheus

Diamond Member
Jun 7, 2005
7,313
2
0
Hey, nice twist :)

Viewable by all here - http://www.nicetechnology.co.uk/animate2.html

My next step was going to be to have the blocks slowly fall away to the bottom of the screen, possibly implementing some physics to bounce around a bit, but this is actually a good one for optimising because it's simple and there's a lot of movement. It uses 100% of one of my 3Ghz cores :confused:

It does work in IE too. No idea why, but it does.

 

heymrdj

Diamond Member
May 28, 2007
3,999
63
91
Looks nice :). Problem is CPU usage. Using FF it sends core 1 form 8.6% to 68% and core 2 from 47% to 73%.
 

lyssword

Diamond Member
Dec 15, 2005
5,630
25
91
Originally posted by: heymrdj
Looks nice :). Problem is CPU usage. Using FF it sends core 1 form 8.6% to 68% and core 2 from 47% to 73%.

Yeah my both cores load at 100%
 

Ken g6

Programming Moderator, Elite Member
Moderator
Dec 11, 1999
16,836
4,815
75
I have an old Athlon XP 1.8GHz; even the new version was too much for it. If I were to rewrite this application from scratch, I'd start with one background PNG of all the squares, and move a bunch of white PNGs up and down over the columns, hiding squares. This old page I made several years ago demonstrates the basic idea with semi-transparent GIFs.

On the other hand, I tried improving the existing code, by generating the New objects just once. With the following end of the script, I get only about 75% load on my one old core:

 

Ken g6

Programming Moderator, Elite Member
Moderator
Dec 11, 1999
16,836
4,815
75
OK, Attach Code is broken. (So is preview.) Let's try a quote:
// Declare squares
var squares = new Array();
var s=(screen.width/11)-2;

function setupSquares() {
//var num=0;
var h=0;
var w=1;
var n=11;
var squareCol
for(var l=0; l<s; l++) {
squareCol = new Array(n);
squares[l] = squareCol;
w+=11;
h=0;
for(var k=0; k<n; k++) {
squareCol[k] = new Square(w,h);
h+=11;
//num++;
}
}
}

function buildSquares(canvas) {
var squareCol;
var n;
canvas.clear();
for(var l=0; l<s; l++) {
n=(Math.random()*10)+1;
squareCol = squares[l];
for(var k=0; k<n; k++) {
squareCol[k].render(canvas);
}
}
}

setupSquares();
animate();
var timer = setInterval("animate()",50);
 

Ken g6

Programming Moderator, Elite Member
Moderator
Dec 11, 1999
16,836
4,815
75
Another change to the Canvas object, and it works even better on IE6 than on FF3!

// Canvas object
function Canvas(div) {
this.div=div;
this.tempHTML = new Array();
this.drawImage = function(src) {
this.tempHTML.push(src);
};
this.displayImage = function() {
this.div.innerHTML = this.tempHTML.join('');
};
this.clear = function() {
this.tempHTML = new Array();
};
}
 

Atheus

Diamond Member
Jun 7, 2005
7,313
2
0
My latest version:

http://www.nicetechnology.co.uk/animate3.html

now released under ATOT licence... whatever that may be...

The other problem, apart from CPU usage, is that these things cause memory to grow into infinity - even when creating no new objects during animation.

/edit:

Actually scratch that. In IE7 memory usage is stable - it's only in FF3 where it keeps climbing. IE7 also seems smoother where FF3 flashes and fickers despite double buffering.
 

Ken g6

Programming Moderator, Elite Member
Moderator
Dec 11, 1999
16,836
4,815
75
By the way, this is all nice as an optimization exercise; but for better graphics in a web browser, you might try either the Canvas element (one predefined in Firefox, Opera, and available by plugin in IE), or editing SVG graphics. Both are likely to be higher-performance for the kind of drawing you seem to want to do.
 

PhatoseAlpha

Platinum Member
Apr 10, 2005
2,131
21
81
If performance is an issue, you should never, ever, ever use string appends. Especially in massive loops like that.

As for plugins.....well, I'd stay away from anything that wasn't built into IE or so widespread that nearly all IE users have it. At least, if usability is any concern. Can't say I'm thrilled with it, but IE is too much of the market to be anything but defining.

*Edit: Actually, nevermind. Ken G6 already pointed that out and gave you a solution. Just remember in the future, string appends in loops are generally catastrophic for performance. *



As for your program itself, from the look of it there's a higher level optimization to be done. Every render you're recreating all of the rows and squares. It should be no surprise that slows things down. Really, I'd expect you'd be much, much better off making it column a div, keeping references to the divs in an array or linked list, and then just making 1 new column every pass and moving all the others to the right. It's bound to be many times faster to adjust 1 variable on 25-50 divs each pass, remove 1, then make 1 new div, then it is to make 25-50 new divs each time.
 

Woosta

Platinum Member
Mar 23, 2008
2,978
0
71
PhatoseAlpha - DOM methods are going to be much slower than innerHTML.
 

PhatoseAlpha

Platinum Member
Apr 10, 2005
2,131
21
81
I suspect as much, but the results I can find aren't very clear on their methodology. I have to wonder how things will shape up if the element you're working on isn't actually in the document when you're working on it.

*edit: Indeed it is, even on non-displayed elements with only one document.getElementById. How odd. I would really expect it to be the other way around.

At any rate, for the OP: FYI, Firebug has a javascript profiler tool built into it. It's extremely handy for figuring out what's collapsing performance if it's not an IE only performance problem.
 

Woosta

Platinum Member
Mar 23, 2008
2,978
0
71
Well the obvious answer is you're drawing like 1500 elements or so every 50 milliseconds, no wonder it's slow. The solution would be to come up with a smarter method of mimicing those little shapes and IMO, you should just draw columns and have background images tiling.. and limit based on the height.
 

Woosta

Platinum Member
Mar 23, 2008
2,978
0
71
Anyways, I don't know if you'll find this useful ( since you were mainly asking about dom element generation in the hundreds ) but I recoded what I thought you wanted -> http://www.cssanswers.com/ ( too lazy to create a subdomain )..

The JS for the lazy:

var addEvent = window.addEventListener ?
function( el, ev, fn ) { el.addEventListener( ev, fn, false ); } :
function( el, ev, fn ) { el.attachEvent( 'on' + ev, fn ); };

if ( typeof Array.prototype.forEach == 'undefined' ) {
Array.prototype.forEach = function(fun) {
var len = this.length;
if ( typeof fun != 'function' )
throw new TypeError;

var thisp = arguments[1];
for (var c = 0; c < len; c++) {
if (c in this)
fun.call(thisp, this[c], c, this);
}
}
}

addEvent( window, 'load', function() {
var width = document.body.clientWidth,
numberOfColumns = Math.round( width / 11 ),
l = 0,
canvas = document.getElementById('canvas'),
columns = [];

for ( var i = numberOfColumns, el; i--; ) {
el = document.createElement('div');
columns.push( el );
randomHeight( el );
el.className+= 'column';
el.style.left = ( l + 'px' );
l+=11;
canvas.appendChild( el );
}

function randomHeight( el ) {
var columnHeight = Math.round( Math.random() * 10 );
el.style.height= ( columnHeight * 11 + 76 ) + 'px';
}

window.setInterval( function() {
columns.forEach( function( el ) {
randomHeight( el )
});
}, 1150);
});

Ughh crappy forum doesnt preserve tabs... AH MY CODE LOOKS UGLY.
 

Atheus

Diamond Member
Jun 7, 2005
7,313
2
0
Originally posted by: Ken g6
By the way, this is all nice as an optimization exercise; but for better graphics in a web browser, you might try either the Canvas element (one predefined in Firefox, Opera, and available by plugin in IE), or editing SVG graphics. Both are likely to be higher-performance for the kind of drawing you seem to want to do.

I've heard about that but I wouldn't want to use anything which is not available to 90% of browsers. It's just an experiment anyway. If this was for a practical job I'd use flash.

Originally posted by: PhatoseAlpha
As for your program itself, from the look of it there's a higher level optimization to be done. Every render you're recreating all of the rows and squares. It should be no surprise that slows things down. Really, I'd expect you'd be much, much better off making it column a div, keeping references to the divs in an array or linked list, and then just making 1 new column every pass and moving all the others to the right. It's bound to be many times faster to adjust 1 variable on 25-50 divs each pass, remove 1, then make 1 new div, then it is to make 25-50 new divs each time.

I get where you're coming from, this method would use less objects and be much faster, but it would be limited to just making dancing squares - I'm not actually trying to make efficient dancing squares I'm trying to make a general javascript rending engine. The dancing squares are just there to produce hundreds of objects to play with.

Originally posted by: Woosta
Well the obvious answer is you're drawing like 1500 elements or so every 50 milliseconds, no wonder it's slow.

Touché :laugh:

The solution would be to come up with a smarter method of mimicing those little shapes and IMO, you should just draw columns and have background images tiling.. and limit based on the height.

See answer to PhatoseAlpha above.

Nicely done on your version though - it hardly uses any CPU at all.

I'll post something more tonight.
 

PhatoseAlpha

Platinum Member
Apr 10, 2005
2,131
21
81
Well, from what you're trying to do it would seem the best way to do it would be to create all the squares you'll need once, store references to them in an array, and then modify those - swapping color and/or display.
 

PhatoseAlpha

Platinum Member
Apr 10, 2005
2,131
21
81
Pretty, but they look like they're using the canvas element mentioned above, and thus brings the baggage of a not-built-into-IE element along with it.