2

In one of my script, I make extensive use of array to temporary store data. The problem I m facing is that I have a lot of code handling the array just so I make economic use of the space.

Should I even bother since Node.js array are associative array?

My current solution is:

//Get the minimum empty id in array
function get_id(callback) {
    var i = 0;
    while(array[i] != null) {
        i = i + 1;
    }
    array[i] = 0;
    callback(i);
}


get_id(function (i) {
    array[i] = {large object};
    //...
    array[i] = null;
});

But I feel it is wrong and bug prone.

Can I just do:

array[i] = {large object};
i = i + 1;
//...
array[i] = null;

Or would it lead to large consumption of memory?

array is a global variable of the module using it.

Cut down code (I ve removed all computing not linked to the array player.active_mission):

var player = {},
    missions = [{time: 1000}];

function end_mission(mission, squad, mission_log, callback) {
    //Make all the computing of the mission to know if the player won...
    callback(mission_log);
}

function get_ami(callback) {
    var i = 0;
    while(player.active_mission[i] != null) {
        i = i + 1;
    }
    player.active_mission[i] = 0;
    callback(i);
}

function wait_mission(mission, squad, mission_log, i, time, callback) {
    setTimeout(function () {
        console.log('End of mission');
        player.active_mission[i] = null;
        end_mission(mission, squad, mission_log, callback);
    }, time);
}

function start_mission(mission, squad, callback) {
    var mission_log = {mission: mission, time_start: new Date(), completed: false, read: false};

    //Verify if the player can start the mission...

    console.log('start_mission');
    get_ami(function (i) {
        player.active_mission[i] = {mission: mission, squad: squad, mission_log: mission_log}
        wait_mission(mission, squad, mission_log, i, missions[mission].time, callback);
    });
}

player.active_mission = [];

//This part is inside get request, after sanitizing all input
start_mission(0, [0, 1], function (r) {
    //r.id = req.session.player_id;
    if(r.error) {
        console.log('start: error: ' + r.error);
    } else {
        console.log('start: Success: ' + r.result);
    }
});

player.active_mission hold all uncompleted request of the player, and need to be saved if the player quit before completion. My problem is just if I should try to keep it with small id, or just go on with .push() and get the id with .length()?

In short: If a array have nothing but null for the 1000 first id, and start having data only at array[1000]`, am I wasting memory?

DrakaSAN
  • 7,673
  • 7
  • 52
  • 94
  • What do you mean by cleaning? – thefourtheye Oct 28 '15 at 10:35
  • Where is `array` coming from (where is it declared, what is its scope and lifetime)? Why do you put the large object in the array at all? – Bergi Oct 28 '15 at 10:36
  • Please show us your full code. – Bergi Oct 28 '15 at 10:38
  • @Bergi: array is a global var in the module, since I have to be able to dump all current request, I ve found it better to get them all in a single array, unstead of trying to get all waiting process and dump them. – DrakaSAN Oct 28 '15 at 10:43
  • What do you mean by "waiting process"? Are you doing anything asynchronous here? Your `get_id` example looks like you're putting the object in the array at the start of your function, and removing it in the end. What are you really doing with it? Actual code would help. – Bergi Oct 28 '15 at 10:50
  • @Bergi: It s a idle game, the player start a mission, wait some time, and get his reward at the end. The array hold all current active mission, that I need to save if he quit in the middle of the waiting. I try to keep the array small by mean of the id, but is it really neccessary? – DrakaSAN Oct 28 '15 at 11:09
  • Yes, you should clean the array of missions that are no longer active. Keeping the ids low is not strictly necessary, though I'd recommend to use an object instead of an array then so that you don't accumulate keys. – Bergi Oct 28 '15 at 11:16
  • If I got it right, you want some kind of *session storage* on your server. There are dedicated modules for that, which will handle all the nitty details for your (especially aging) so that you don't leak memory. – Bergi Oct 28 '15 at 11:17
  • @Bergi: I ve tried some of thoses module, but they go way beyond what I need. All I need is a way to quickly save the state of the player, in a form I can load later, a variable like that is more than enought and currently work well. I m just wondering if I ll have memory issue if the id grow too much. – DrakaSAN Oct 28 '15 at 11:19
  • @DrakaSAN: No, you won't get memory issues from too high ids (JS arrays handle that quite well), you will get memory issues if you don't consistently clean up after yourself. As you said, your state store is a global variable, and you will need to manage memory manually with those. – Bergi Oct 28 '15 at 11:21
  • @Bergi: That s what I needed to know, could you put it in a answer? – DrakaSAN Oct 28 '15 at 11:24
  • @DrakaSAN: Ah well, you *will* get issues if you constantly fill your array using ever higher ids, I only meant that cleaning up the `{large object}` is more important and if you did not do *that* it would get you in trouble much faster. See my answer :-) – Bergi Oct 28 '15 at 11:32

2 Answers2

1

NodeJS has a garbage collector to destroy unreachable object/array/variable.

So when you do array[i] = {large object};, the large object will be in the memory and it will stay here. When you do array[i] = null;, the garbage collector will erase the large object (only if there's no other reference to this object of course).

So yes, it is always good to remove references to useless objects to let the garbage collector clean it.

The impact on the memory of an array of 1000 null (or undefined) will not be very big.

If you want to preserve your memory, you should use an object instead of an array. You can use it with this syntax :

var obj = {};
obj[id] = {large object};

// Free the id
delete obj[id];
Magus
  • 14,796
  • 3
  • 36
  • 51
  • You should use `array.splice(i, 1);` instead of `array[i] = null` This will preserve the keys in the array. – Andreas Louv Oct 28 '15 at 10:51
  • 1
    http://stackoverflow.com/questions/33389202/array-splice-vs-setting-a-value-to-null – Magus Oct 28 '15 at 10:59
  • In either case I will delete the large object, but I need to keep id intact, so will use `= null`. My problem is just, should I try to keep id smallest as possible? If the array have nothing but null until `array[1000]`, am I wasting memory or not? – DrakaSAN Oct 28 '15 at 11:11
  • 1
    @dev-null: No, `splice` (apart from being slow) does not work for the OP who uses the indices in the array as identifiers. – Bergi Oct 28 '15 at 11:14
  • `It is always good to remove references` is quite an overstatement. JS is a garbage collected language, so in general it is a mistake to polute your code with cleansing. Only in very specific cases it makes sense to start removing references. – bgusach Oct 28 '15 at 11:16
  • @dev-null what is wrong with preserving your keys if you need to preserve them? – webduvet Oct 28 '15 at 11:27
1

Can I just do:

i = i + 1;
array[i] = null;

Or would it lead to large consumption of memory?

Yes, considering that array is a global variable and won't get garbage-collected itself, filling it constantly with values (even if only null ones) will eventually let you run out of memory.

Your get_id approach that recycles unused ids does work, but is horribly inperformant - it requires linear time to find a new id. So it'll work for few users with few concurrent missions, but it won't scale.

You'll rather want to use an object and delete keys from it, then you don't get into problems when just counting up:

var count = 0;
var missions = {};

function somethingThatNeedsTheStore() {
    var id = count++;
    missions[id] = …;
    // later
    delete missions[id];
}
// repeatedly call somethingThatNeedsTheStore()

Or actually, on recent node versions, you should consider using a Map instead:

var count = 0;
var missions = new Map;

function somethingThatNeedsTheStore() {
    var id = count++;
    missions.set(id, …);
    // later
    missions.delete(id);
}
// repeatedly call somethingThatNeedsTheStore()
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I was under the impression that `delete` was itself unperformant, as it more or less caused a hole in the allocated memory for the object, leading to fragmentation? – Jared Smith Oct 28 '15 at 11:39
  • 1
    @JaredSmith: More or less, in record-like objects (think `struct`). However, after some time the engine will realize you are using the object in *dictionary mode*, and do it much more efficiently. It will eventually reclaim memory (if not re-used by other keys), which is crucial for your case. Not `delete`ing the property will constantly grow your object as it would grow the array. – Bergi Oct 28 '15 at 11:42
  • @Bergi: Map seems really interesting, thanks a lot :) – DrakaSAN Oct 28 '15 at 12:16
  • @Bergi in that case I think there's a fair amount of FUD being propagated about `delete` out in the wild. Thank you for the clarification. – Jared Smith Oct 28 '15 at 12:41