21

I came across this small JavaScript Program (on Khan Academy) written by someone else:

/*vars*/
frameRate(0);
var Sz=100;
var particles=1000;
scale(400/Sz);
var points=[[floor(Sz/2),floor(Sz/2),false]];
for(var i=0;i<particles;i++){
    points.push([floor(random(0,Sz)),floor(random(0,Sz)),true]);
}
var l=points.length-1;
var dirs=[[0,1],[1,0],[0,-1],[-1,0]];
/*functions*/
var move=function(p1){
    var mv=dirs[floor(random(0,4))];
    var temp=true;
    for(var i=l;i>=0;i--){
        if(!points[i][2]&&points[i][0]===p1[0]+mv[0]&&points[i][1]===p1[1]+mv[1]){
            temp=false;
            p1[2]=false;
            i=0;
        }
    }
    if(temp){
        p1[0]+=mv[0];
        p1[1]+=mv[1];
        if(p1[0]<0){p1[0]=0;}
        if(p1[0]>Sz){p1[0]=Sz;}
        if(p1[1]<0){p1[1]=0;}
        if(p1[1]>Sz){p1[1]=Sz;}
    }
};
/*draw*/
draw= function() {
    background(255);
    for(var i=points.length-1;i>=0;i--){
        stroke(0);
        if(points[i][2]){
            move(points[i]);
        }
        else{
            stroke(0,0,255);
        }
        point(points[i][0],points[i][1]);
    }
};

I looked at the code and found it a bit difficult to read. So I decided to make my own version with some object orientation:

// apparently, object orientation is a lot slower than just putting the data in arrays

var Point = function(x, y) {
    this.x = x;
    this.y = y;
    this.moving = true;
};

// static constant
Point.dirs = [
    {x:0, y:1},
    {x:1, y:0},
    {x:0, y:-1},
    {x:-1, y:0}
];

/*vars*/
frameRate(0);
var Sz=100;
var particles=1000;
scale(400/Sz);

// first point
var points=[new Point(floor(Sz/2), floor(Sz/2))];
points[0].moving = false;  // blue

// remaining points
for(var i=0;i<particles;i++){
    points.push(new Point(floor(random(0, Sz)), floor(random(0, Sz))));
}
var l=points.length-1;

/*functions*/
var move = function(p1){
    var mv = Point.dirs[floor(random(0,4))];
    var notAttached = true;
    for(var i = l; i >= 0; i--) {
        if(!points[i].moving && points[i].x === p1.x + mv.x && points[i].y === p1.y + mv.y) {
            notAttached = false;
            p1.moving = false;
            i = 0;
        }
    }
    if (notAttached) {
        p1.x += mv.x;
        p1.y += mv.y;
        if (p1.x < 0) { p1.x = 0; }
        if (p1.x > Sz) { p1.x = Sz; }
        if (p1.y < 0) { p1.y = 0; }
        if (p1.y > Sz) { p1.y = Sz; }
    }
};
/*draw*/
draw= function() {
    background(255);
    for(var i=points.length-1; i >= 0; i--) {
        stroke(0);
        if (points[i].moving) {
            move(points[i]);
        }
        else {
            stroke(0, 0, 255);
        }
        point(points[i].x, points[i].y);
    }
};

The original just uses arrays for data. Index [0] is an x coordinate, index [1] is a y coordinate, index [2] is a flag. I think the only changes I made were just what was needed to replace point[0] with point.x, etc. but I was surprised by how much slower my version was.

Is there a better way to make the code more readable without losing performance? or do we have to lose performance for readability?

JavaScript Engine: Chrome in Windows 10

Edit: more information discovered:

As Ryan pointed out, using plain objects instead of a Point class – new Point(x, y){x: x, y: y, moving: false} - improved performance close to the original. So it was just the Point class that made it slow.

So now working with 3 different versions of the program:

  • array data ( original )
  • Point class ( 1st rewrite )
  • plain object ( 2nd rewrite )

In Chrome, the array data and plain object have no easily noticeable difference in performance, the Point class is noticeably slower.

I installed Firefox to test it, and found all three versions to be close to the same performance as each other.

Just eyeballing it, the Firefox speed seems to be in between the slow and the fast speeds I get from Chrome, probably closer to the fast end.

beauxq
  • 1,258
  • 1
  • 13
  • 22
  • How are you testing performance? Just one run or many? – Avery Dec 25 '17 at 04:55
  • @Avery Just observing the movement of the dots on the canvas. A few runs on each all produced a noticeable difference. – beauxq Dec 25 '17 at 04:58
  • Which browser? Also, try plain objects – `new Point(x, y)` → `{x: x, y: y, moving: false}`. (FWIW, performance looks the same on Firefox 57.) – Ry- Dec 25 '17 at 05:01
  • @Ryan Using plain objects brought the performance back to the original (or at least close to it, no easily noticeable difference). So I guess it's just the prototype that was slowing it down? – beauxq Dec 25 '17 at 05:12
  • Kind of a long shot, but what happens if you make `dirs` a variable instead of a property of `Point`? – Ry- Dec 25 '17 at 05:36
  • @Ryan That was one of the first things I tried, couldn't see any difference. Using plain objects instead of the class was all I needed to get back to the original faster performance. – beauxq Dec 25 '17 at 05:42
  • 1
    Pro tip: change `Point` in to a function that returns an object. Then get rid of the `new`. Then you get the advantage of unburdened plain objects and the rest of your code can stay exactly the same: `function Point(x, y) { return { x: x, y: y, moving: false }; } .... points.push(Point(floor(random(0, Sz)), floor(random(0, Sz))));`. – JLRishe Dec 25 '17 at 07:12
  • OO is slower and much more memory demanding by default :) – Martin Chaov Dec 29 '17 at 23:20
  • My recommendation if you want performance AND readability is to use Emscripten or Ecmascript. Emscripten is best for performance if youre making a game, Ecmascript if you're making a website. Remember, the higher level you go in programming language the more readable it is but with less performance. Best performance comes from Assembly, worst from something like batch scripts. The best compromise is a lanhuage that is processed into a better performing language, like Ecmascript that gets turned into regular javascript. – Simon Hyll Dec 30 '17 at 12:04
  • @SimonHyll I agree with you, but please allow me to correct you in one point: JavaScript is an implementation of Ecmascript, so they are basically the same. What you are referring to with Ecmascript is its more recent versions, ECMAScript 2015, also known as ES6, and the newer versions beyond that. But it's all JavaScript, actually. – Patrick Hund Dec 31 '17 at 11:31
  • As said above, using modern JavaScript (namely `Array.forEach` and `Array.some` defined in ECMA 5) may help getting faster loops than a manual `for`, since they are built into the browser. Also you can manage your points in two independent arrays to improve readability and somehow simplify the `draw` function, as in [this example](https://www.khanacademy.org/computer-programming/two-arrays-es5-version-of-building-snowflakes/6488467040043008). – GnxR Dec 31 '17 at 15:46
  • I believe this question belongs to codereview.stackexchange.com – Igor Soloydenko Jan 01 '18 at 08:07
  • [This](https://stackoverflow.com/questions/42044861/factory-vs-constructor-function-performance-in-javascript) may be related and of some help to you – Philipp Wrann Jan 02 '18 at 09:28
  • Since this seems really a question of objects vs arrays - I think its a duplicate of this - https://stackoverflow.com/questions/17295056/array-vs-object-efficiency-in-javascript – Ctznkane525 Jan 12 '18 at 18:29

2 Answers2

1

That is why people use bundlers like webpack to make readable code more efficient. Checkout https://webpack.js.org/

Anthony Delgado
  • 87
  • 1
  • 1
  • 7
1

For sure you are not the only programmer that sees/will see this code (probably some of the programmers are beginners which it will be hard to even understand the code).

For your code I will choose readability instead of performance!

I would get rid of this and new. Is this the global object or undefined? I cannot tell from your example! You have to know in which context you are.

Premature optimization is the root of all evil. Donald Knuth.

Browsers have become very good on optimizing the code we wrote.

You can test the speed of your program by using performance.now() which is quite accurate:

var t1 = performance.now() 
//your code
var t2 =  performance.now()
console.log(t2-t1);

Or you can use jsperf (https://jsperf.com/). I am sure out there are other several websites with this facility.

Great comment from JLRishe: More readable JavaScript code is slower? which I totally agree.

aciurea
  • 51
  • 6