0

I've made a script where there are supposed to be little balls that attract eachother in real time. The problem it is EXTREMELY slow. I used animation frame, so I think it should be updating every frame, but it isn't. Here is the code:

$(function() {

  var mouseDown
  var c = document.getElementById('myCanvas');
  var ctx = c.getContext("2d");
  var objects = []

  c.addEventListener("mousedown", onMouseDown);
  c.addEventListener("mouseup", onMouseUp);

  function createSquare(x, y, size, direction, xVel, yVel) {
    this.x = x;
    this.y = y;
    this.size = size;
    this.drawStylus = drawStylus;
  };

  function drawStylus() {
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI);
    ctx.fill();
  };

  function getDistance(x1, y1, x2, y2) {
    return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
  }

  function draw() {
    ctx.clearRect(0, 0, 5000, 5000);
    for (i = 0; i < objects.length; i++) {

      var x = objects[i][0]
      var y = objects[i][1]
      var size = objects[i][2]
      var dir = Math.random() * Math.PI * 2
      var force = 0
      var xVel = 0
      var yVel = 0
      for (n = 0; n < objects.length; n++) {
        if (n != i) {
          force = 100 * objects[n][2] / getDistance(x, y, objects[n][0], objects[n][1])
          angle = Math.atan2(y - objects[n][1], x - objects[n][0])
          xVel += force * -Math.cos(angle)
          yVel += force * -Math.sin(angle)
          window.requestAnimationFrame(draw)
        };
      };

      ctx.beginPath();
      ctx.arc(x + xVel, y + yVel, size, 0, 2 * Math.PI);
      ctx.fill();
    };
  };

  function onMouseDown() {
    mouseDown = true
    x = event.clientX
    y = event.clientY
    size = 100

    animation = function() {
      size = size + 20

      var cursorSquare = new createSquare(x, y, size);
      cursorSquare.drawStylus();
      anim = window.requestAnimationFrame(animation)
    };
    window.requestAnimationFrame(animation)
  };

  function onMouseUp() {
    if (mouseDown) {
      window.cancelAnimationFrame(anim)
      var newSquare = new createSquare(x, y, size);
      objects.push([x, y, size])
      mouseDown = false
    };
  };

  function loop() {
    draw();
    window.requestAnimationFrame(loop);
  };

  function init() {
    loop();
  };

  init()

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<canvas id='myCanvas' width="5000" height="5000" style="border:1px solid #000000;"></canvas>

6
  • You clear a 5000 * 5000 area every frame, that's going to be really really slow Commented Mar 20, 2016 at 14:56
  • I just tried it with only 700 * 700 and it still has the same issue :( Commented Mar 20, 2016 at 15:07
  • You're creating lots of global variables. Don't know if it's having any impact on your code, but I'd clean that up first. Commented Mar 20, 2016 at 15:26
  • @squint which variables are global? They all appear to be defined within the scope of the DOM ready function or the functions within it. Commented Mar 20, 2016 at 15:30
  • @Andy: Anything that is missing var that is not already defined at the top of the ready handler. I see i, n, x, y, size, animation, anim. Commented Mar 20, 2016 at 15:33

2 Answers 2

1

You are calling requestAnimationFrame for each object, this is the wrong way to use requestAnimationFrame (RAF).

You should only call it once per frame not once per object.

function mainLoop(time){ // main loop RAF will add the time in milliseconds to the arguments.
    ctx.clearRect(0,0,canvas.width,canvas.height); // clear
    draw(); // call the draw loop
    requestAnimationFrame(loop); // request next frame
}
requestAnimationFrame(loop); // request next frame

Using the draw functions like ctx.arc is very slow. You will get much better performance if you render images instead ctx.drawImage. You can create a canvas, draw the arc on that canvas and then draw that canvas with ctx.drawImage(canvasImage,... to get a much faster update.

The other answer advised you to use forEach, don't use forEach or any of the array functions that involve callbacks as they are MUCH slower than using standard loops (for, while, do)

UPDATE

As things change rapidly in the browser world I have tested the use of forEach in this case and in this case the news is not good. forEach still adds a significant additional overhead on each iteration when compared to for, while , and do while

The important thing to note (and why I striked out the last paragraph) is that the overhead is per iteration, if you have a small number of iterations and a large amount of code per iteration then the overhead is insignificant and not worth the bother, personal coding style should make the choice of what style to use in those cases.

If on the other hand you have a large number of iterations and a small amount of processing per iteration then using forEach will significantly impact the performance of the loop.

This holds true for Chrome, Edge, and Firefox with all showing the standard iteration (for loops) with inline code (not calling a function) to be the quickest, next and 10% slower than standard iteration is standard iteration with a function call (like forEach), and then forEach with an additional overhead per iteration of over 2X. (each test used a 15-20 to 1 code balance, that is the code inside the iteration is 15-20 times longer than the minimum code required to iterate. So one line for the for, forEach loop and 10-15 lines of code inside the loop.)

If you are handling an array of a few thousand to tens of thousands the difference is not worth bothering with, If you are handling 100s of thousands to millions plus you should avoid forEach.

Note: I did not test forEach on typed arrays as that is not applicable in this case.

Tested on

  • Chrome Version 50.0.2661.37 beta-m
  • Firefox 46.0b2
  • Edge 25.10586
Sign up to request clarification or add additional context in comments.

5 Comments

This seems all correct to me. One note though. forEach is significantly faster (~10x) on typed arrays almost without exception. But forEach can also be much faster on regular arrays. The speed depends very much on what you are doing. I personally have found that current edge browsers do forEach faster every time. You should always test it first.
@Andrew I do test these things regularly but must admit I have not tested forEach (and the like) for some months. Will have a look again and change my answer if forEach has finally made its self worthy of use.
You may still find them slower; again, it depends on what you are doing and how you are doing it. My callbacks are typically extremely simple (and usually immutable). The last time I had to do a performance test (about six weeks ago), I found that forEach iterating over a regular array was almost 20x faster -- so I assumed I did it wrong. Retested and, again, ~17x faster. I really suspect it depends on your code and what optimisations your JS engine can perform. But I think if perf is important or you have a perf issue (like in the question), switching may help a lot.
@Andrew I can not get the performance figures you mention, I get standard loops outperforming forEach in all cases. Though I am on win10 32bit OS running on a 64bit machine. Maybe the 32bit OS is the problem
Totally fair comment. I hate overly trivial examples, but... this is an overly trivial example of the tests I would run to test a function. Real-world performance will depend on a lot of things, such as mutation over object creation (memory allocation) and so on. In this case, the (osx) performance boost is 7x in Chrome and, well, off the scale in FF. Didn't test IE/Edge. Someone else's code will perform totally differently. BTW, I appreciate another coder who perf-tests his/her own code.
0

A couple of things that might help.

Take objects.length out of the for loop and assign it to a var before you start the loop. Currently your counting the length of objects on every interaction of your loop.

Better yet use objects.forEach to iterate over the arrays.

Lastly why does draw() call itself at the bottom of the two for loops? This is going to fill up the event loop very quickly and suspect the main reason for the slow down.

3 Comments

JS doesn't count the objects when you access .length on an Array, and .forEach() is more likely to be slower than a for loop.
Thanks, that fixed the issue with crashing, but the particles still don't move in real time... Am I doing something wrong?
@OwM You have huge particles, filling a big space, especially if it is a circle, takes a lot of time

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.