NodeJS, Promises and performance

2019-01-15 17:23发布

问题:

My question is about performance in my NodeJS app...

If my program run 12 iteration of 1.250.000 each = 15.000.000 iterations all together - it takes dedicated servers at Amazon the following time to process:

r3.large: 2 vCPU, 6.5 ECU, 15 GB memory --> 123 minutes

4.8xlarge: 36 vCPU, 132 ECU, 60 GB memory --> 102 minutes

I have some code similair to the code below...

start();

start(){

  for(var i=0; i<12; i++){

      function2();    // Iterates over a collection - which contains data split up in intervals - by date intervals. This function is actually also recursive - due to the fact - that is run through the data many time (MAX 50-100 times) - due to different intervals sizes...
    }
}

function2(){

  return new Promise{

    for(var i=0; i<1.250.000; i++){       
         return new Promise{      
            function3();      // This function simple iterate through all possible combinations - and call function3 - with all given values/combinations
         }
      }   
   } 
}


function3(){
   return new Promise{ // This function simple make some calculations based on the given values/combination - and then return the result to function2 - which in the end - decides which result/combination was the best...
}}

This is equal to 0.411 millisecond / 441 microseconds pér iteration!

When i look at performance and memory usage in the taskbar... the CPU is not running at 100% - but more like 50%...the entire time? The memory usage starts very low - but KEEPS growing in GB - every minute until the process is done - BUT the (allocated) memory is first released when i press CTRL+C in the Windows CMD... so its like the NodeJS garbage collection doesn't not work optimal - or may be its simple the design of the code again...

When i execute the app i use the memory opt like:

node --max-old-space-size="50000" server.js

PLEASE tell me every thing you thing i can do - to make my program FASTER!

Thank you all - so much!

回答1:

It's not that the garbage collector doesn't work optimally but that it doesn't work at all - you don't give it any chance to.

When developing the tco module that does tail call optimization in Node i noticed a strange thing. It seemed to leak memory and I didn't know why. It turned out that it was because of few console.log() calls in various places that I used for testing to see what's going on because seeing a result of recursive call millions levels deep took some time so I wanted to see something while it was doing it.

Your example is pretty similar to that.

Remember that Node is single-threaded. When your computations run, nothing else can - including the GC. Your code is completely synchronous and blocking - even though it's generating millions of promises in a blocking manner. It is blocking because it never reaches the event loop.

Consider this example:

var a = 0, b = 10000000;

function numbers() {
  while (a < b) {
    console.log("Number " + a++);
  }
}

numbers();

It's pretty simple - you want to print 10 million numbers. But when you run it it behaves very strangely - for example it prints numbers up to some point, and then it stops for several seconds, then it keeps going or maybe starts trashing if you're using swap, or maybe gives you this error that I just got right after seeing the Number 8486:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
Aborted

What's going on here is that the main thread is blocked in a synchronous loop where it keeps creating objects but the GC has no chance to release them.

For such long running tasks you need to divide your work and get into the event loop once in a while.

Here is how you can fix this problem:

var a = 0, b = 10000000;

function numbers() {
  var i = 0;
  while (a < b && i++ < 100) {
    console.log("Number " + a++);
  }
  if (a < b) setImmediate(numbers);
}

numbers();

It does the same - it prints numbers from a to b but in bunches of 100 and then it schedules itself to continue at the end of the event loop.

Output of $(which time) -v node numbers1.js 2>&1 | egrep 'Maximum resident|FATAL'

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory
    Maximum resident set size (kbytes): 1495968

It used 1.5GB of memory and crashed.

Output of $(which time) -v node numbers2.js 2>&1 | egrep 'Maximum resident|FATAL'

    Maximum resident set size (kbytes): 56404

It used 56MB of memory and finished.

See also those answers:

  • How to write non-blocking async function in Express request handler
  • How node.js server serve next request, if current request have huge computation?
  • Maximum call stack size exceeded in nodejs
  • Node; Q Promise delay
  • How to avoid jimp blocking the code node.js