6
\$\begingroup\$

This code consumes a lot of CPU. Could you tell me where it is possible to improve the code or change the way rendering?

Or it is possible to reduce the overhead of calculations? If you are using only 1 point when drawing the load on the CPU is not changed! Load 50-60%

Ubuntu 15.04 / intel® Core ™ i5-3230M CPU @ 2.60GHz × 4

Run the code without rendering lines - 20-30% load. So they are quite costly...

Demo: http://codepen.io/jonfint/full/VLYMMW

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext('2d'),
    points = [],
    r = 0;

var a = 50; // количество точек
var b = 1; // скорость поворота
var d = 20; // увеличение радиуса*
var minDist = 200; // ??
var dist; // ??

canvas.width = 500;    // Originally window.innerWidth, changed for Stack Snippet
canvas.height = 600;   // Originally window.innerHeight, changed for Stack Snippet

for (var i = 0; i < a; i++) {
    var rand = Math.random() * canvas.height;

    points.push({
        cy: rand,
        cx: rand * 0.3 + 300,
        r: 360 / a * i,
        p: {x: null, y: null},
        d: Math.random() * (d + 5) + 50,
        s: (Math.random() - 0.5) * 0.7,
        size: Math.random() * 3 + 1,
    })
}

function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.fillStyle = '#202020';
    for (var i=0, len = points.length; i < len; i++) {
        var p = points[i];

        for (var j = i + 1; j < points.length; j++) {
            var p2 = points[j];
            distance(p.p.x, p.p.y, p2.p.x, p2.p.y);
        }

        p.r += p.s;

        var vel = {
            x: p.d * Math.cos(p.r * Math.PI / 180),
            y: p.d * Math.sin(p.r * Math.PI / 180) / 2
        };


        var centx, centy;
        centx = p.p.x - p.size * 0.5;
        centy = p.p.y - p.size * 0.5;
        ctx.beginPath();
        ctx.rect(centx, centy, p.size, p.size);
        ctx.fill();
        ctx.closePath();

        p.p.x = p.cx + vel.x;
        p.p.y = p.cy + vel.y;
    }
}

function distance(p1x, p1y, p2x, p2y) {
    var dx = p1x - p2x;
    var dy = p1y - p2y;
    dist = Math.sqrt(dx*dx + dy*dy);
      // Нарисовать линию, если расстояние меньше, чем minDistance
    if (dist <= minDist) {
        ctx.beginPath();
        ctx.strokeStyle = 'rgba(200, 200, 200,'+ (1.0-dist/minDist) +')';
        ctx.moveTo(p1x, p1y);
        ctx.lineTo(p2x, p2y);
        ctx.lineWidth = 1;
        ctx.stroke();
        ctx.closePath();
    }  
}

window.requestAnimFrame = (function(){
    return  window.requestAnimationFrame   ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame    ||
        window.oRequestAnimationFrame      ||
        window.msRequestAnimationFrame     ||
    function(callback) {
        window.setTimeout(callback, 1000 / 30);
    };
})();

(function animloop() {
    requestAnimFrame(animloop);
    render();
})();
<canvas id="canvas"></canvas>

\$\endgroup\$
  • \$\begingroup\$ At first glance: If you have used a 3D context, your code would have been GPU-accelerated... \$\endgroup\$ – Junius Rendel Apr 27 '15 at 14:30
  • \$\begingroup\$ How important is it that the lines between the points is always 100% accurate (as far as "draw the line if dist < 200)? Could you calculate that every 10 frames instead of every frame? \$\endgroup\$ – Charlie Wynn Apr 27 '15 at 14:33
  • \$\begingroup\$ Run the code without rendering lines - 20-30% load. So they are quite costly... \$\endgroup\$ – Jonny B Apr 27 '15 at 14:51
  • \$\begingroup\$ Just to clarify — is it supposed to be a kinetic sculpture rather than a rigid body? \$\endgroup\$ – 200_success Apr 27 '15 at 17:33
  • \$\begingroup\$ Similar to DNA. I did not know that on this site, you can run the code. Link demo - is the work code. \$\endgroup\$ – Jonny B Apr 27 '15 at 17:45
3
\$\begingroup\$

I see these performance reducers in your code:

  • Resetting context state is modestly expensive--especially when done inside a loop. Don't needlessly reassign ctx.fillStyle = '#202020' inside your render loop. Just do it once at the start of your app.

  • Math trig methods are expensive. Prebuild the Math.cos & Math.sin values you need into a lookup table. You may need to confine your p.r values to a slightly more limited set of values matching the lookup table.

  • Math.sqrt is extremely expensive-especially when done within nested loops (50*50==2500 distance calculations per animation frame!). Instead, do the equally effective test on the squared values. This fix may get you "the most bang for the buck":

    // at the top of your app
    var minDistSquared = minDist*minDist;
    
    // in your distance function
    var dx = p1x - p2x;
    var dy = p1y - p2y;
    dist = Math.sqrt(dx*dx + dy*dy);
      // ?????????? ?????, ???? ?????????? ??????, ??? minDistance
    if (dx*dx+dy*dy <= minDistSquared) {
        // do stuff
    }
    
  • Resetting context state is modestly expensive--especially when done inside a loop. You'll need to perf-test this one: Resetting the opacity using context.globalAlpha may be faster than resetting the alpha value of the context.strokeStyle. Or alternatively, confine your opacities to a smaller set and sort the points based on opacity before drawing them (again, perf-test required).

  • Calling a function is slightly expensive--more so when done inside a loop (50*50==2500 times). Move your function distance calculation & drawing inside function render.

  • Minor improvement--almost not worth mentioning: use while loops to iterate through points: var pointsCountdown=points.length and then while(--pointsCountdown)

\$\endgroup\$
3
\$\begingroup\$

I profiled your code and calling your render function takes 3.1-4.2 ms on my machine (i7 2.1 ghz running chrome/linux). I managed to cut that to only 0.6 ms by removing what is AFAIK the performance killer in this type of drawings using the canvas API. Check it out for yourself http://codepen.io/anon/pen/QbwQmo

The performance killer that I'm talking about is that you're drawing a million separate paths instead of batching them all in a single .beginPath ... .stroke. The catch is however that you can't have multiple colors (alpha levels) in the same path - so changing the color for every segment is not possible anymore. You could instead bucketize the paths - choose 10 shades of grey and add segments to these buckets; then call .stroke for each bucket (that'll amount to max 10 calls - way better than (50*50 which is what you can have)). The only way to overcome this limitation of the canvas API is to not use it in the first place :). Instead use a WebGL renderer (pixi.js, goo.js, three.js).

There's another thing which you can do to increase performance especially if you'll want more than 50 points. Right now you're checking every point against every other point. The number of calls to distance (not the best name btw) grows quadratically with the number of points. You can reduce the number of checks by discarding points that are too far - and you achieve this by splitting the whole space ([0...600] x [0...600]) into smaller buckets of 100 x 100 or something. You'd have to update the buckets every frame such that they contain the points that lie within their bounds. The upside is that you only need to check points in nearby buckets and not in the whole space.

(Disclaimer: I am a dev of goo.js)

\$\endgroup\$
0
\$\begingroup\$

As stated by http://www.html5rocks.com/en/tutorials/canvas/performance/ , it is noticeably quicker to draw a complex path all at once than to draw each line individually, because the code will have to access the graphics card multiple times. Changing the style on the context also takes a bit of time.

So if you are drawing many many lines of the same color and thickness, you should only call one beginPath before a loop and one stroke function to draw all lines at once. In your case, you seem to be choosing the color programatically, so I don't see how you could improve that.

\$\endgroup\$
  • \$\begingroup\$ Part of the effect is that each line segment has a different opacity so while drawing a single polyline is faster, it negates the effect ;-) \$\endgroup\$ – markE Apr 27 '15 at 16:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.