javascriptarraysnode.jsresourcesresource-management

Reduce resource usage of this JS program?


I wrote the following JS program, which i'm running from the command line using node.

//step one: create an array of remaining creatures
var remainingCreatures = [];

//some config variables:
var amt = 1000; //amount of creatues generated
var maxhlt = 1000; //max value for creature health stat
var minhlt = 100; //min value for creature health stat
var maxatk = 100; //max value for creature attack stat
var minatk = 1; //min value for creature attack stat

function remove(target) { //helper function to easily remove creatues from the array
  var index = remainingCreatures.indexOf(target);
if (index > -1) {
  remainingCreatures.splice(index, 1);
}

}

//generate creatures
//each creature has an Attack, Health, and Aggressiveness , as well as Instabillity, the average of the three
//A creature's Instabillity is added to the attack points of its opponent in fights.
for (var i = 0; i < amt; i++) {
  var atkVal = Math.floor((Math.random() * maxatk) + minatk);
  var hltVal = Math.floor((Math.random() * maxhlt) + minhlt);
  var insVal = (atkVal + hltVal) / 2;

  remainingCreatures[i] = {num: i, atk: atkVal, hlt: hltVal, ins: insVal, fts: 0, ihlt: hltVal};
}
console.log(amt + " creatues generated, starting melee...");



function fight(cr1, cr2) { //function to handle fighting, randomly creates creature pairs and has them fight
  function attack(a, b) {
         console.log("[ROUND 1] Creature 1 (#" + a.num + ") is attacking first.");
    b.hlt = b.hlt - (a.atk + b.ins);
    console.log("[HIT] Creature #" + b.num + " health reduced to " + b.hlt);
    if (b.hlt <= 0) {
      console.log("[DEATH] Creature #" + b.num + " Eliminated");
      remove(b);
    } else {
      console.log("[ROUND 2] Creature 2 (#" + b.num + ") is attacking second.");
      a.hlt = a.hlt - (b.atk + a.ins);
      console.log("[HIT] Creature #" + a.num + " health reduced to " + a.hlt);
      if (a.hlt <= 0) {
      console.log("[DEATH] Creature #" + a.num + " Eliminated");
      remove(a);
    }
    }
  }
  console.log("[FIGHT] Pair generated: Creature #" + cr1.num + " and Creature #" + cr2.num);
  cr1.fts++;
  cr2.fts++;
   if (cr1.ins <= cr2.ins) {
    attack(cr1, cr2);
   } else {
    attack(cr2, cr1);
   }
}


for(;true;) {
  if(remainingCreatures.length == 1) break;
  fight(remainingCreatures[Math.floor(Math.random() * remainingCreatures.length)], remainingCreatures[Math.floor(Math.random() * remainingCreatures.length)]);
}

console.log(" ");
console.log("[WIN] Creature #" + remainingCreatures[0].num + " has won!");
console.log("Starting array size was " + amt + " creatures")
console.log(remainingCreatures[0]);

For some reason, this starts to slow down and eventually choke when amt is set to really big numbers, like one million. For reference, that's the number of objects that will be generated and added to an array - as you can probably see in the code, this array gets looped through a lot. But even with one million objects, each object is only around 80 bytes, maximum. And the calculations this program is doing are very basic.

So my question is: why is running this program so resource intensive, and how can I fix or mitigate it without changing the function of the program too drastically?


Solution

  • First of all, a million of anything will take a toll on performance, no matter how small. The other issue is that your design is inherently inefficient.

    Look at your remove() function. It first finds the index of the element, then removes it. If you have one million elements in that array, on average, it will need to compare the passed value with 500,000 elements just to find it before it can remove. There's one easy way to fix this; pass indices directly rather than using .indexOf.

    function fight(ind1, ind2) { //function to handle fighting, randomly creates creature pairs and has them fight
      var cr1 = remainingCreatures[ind1];
      var cr2 = remainingCreatures[ind2];
      function attack(a, b) {
             console.log("[ROUND 1] Creature 1 (#" + a.num + ") is attacking first.");
        b.hlt = b.hlt - (a.atk + b.ins);
        console.log("[HIT] Creature #" + b.num + " health reduced to " + b.hlt);
        if (b.hlt <= 0) {
          console.log("[DEATH] Creature #" + b.num + " Eliminated");
          remove(ind2);
        } else {
          console.log("[ROUND 2] Creature 2 (#" + b.num + ") is attacking second.");
          a.hlt = a.hlt - (b.atk + a.ins);
          console.log("[HIT] Creature #" + a.num + " health reduced to " + a.hlt);
          if (a.hlt <= 0) {
          console.log("[DEATH] Creature #" + a.num + " Eliminated");
          remove(ind1);
        }
        }
      }
      console.log("[FIGHT] Pair generated: Creature #" + cr1.num + " and Creature #" + cr2.num);
      cr1.fts++;
      cr2.fts++;
       if (cr1.ins <= cr2.ins) {
        attack(cr1, cr2);
       } else {
        attack(cr2, cr1);
       }
    }
    
    
    for(;true;) {
      if(remainingCreatures.length == 1) break;
      fight(Math.floor(Math.random() * remainingCreatures.length), Math.floor(Math.random() * remainingCreatures.length));
    }
    

    There aren't many other ways to easily improve performance, but that alone should be enough. To make your code more readable, though, consider replacing this:

    for(;true;) {
      if(remainingCreatures.length == 1) break;
        fight(Math.floor(Math.random() * remainingCreatures.length), Math.floor(Math.random() * remainingCreatures.length));
    }
    

    with this:

    // Consider using === and !== as best practice, but not necessary here
    while (remainingCreatures.length != 1)
        fight(Math.floor(Math.random() * remainingCreatures.length), Math.floor(Math.random() * remainingCreatures.length));
    
    

    (See this link for info on that comment.)