Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to break up a long running function in javascript, but keep performance

Tags:

javascript

I have a long running function. Which iterates through a large array and performs a function within each loop.

longFunction : function(){
       var self = this;
       var data = self.data;

       for(var i=0; len = data.length; i<len; i++){
              self.smallFunction(i);
       }
},
smallFunction : function(index){

// Do Stuff!

}

For the most part this is fine but when I am dealing with arrays above around 1500 or so we get to the point of recieving a javascript execution alert message.

So I need to break this up. My first attempt is like so:

longFunction : function(index){
       var self = this;
       var data = self.data;


      self.smallFunction(index);

      if(data.slides[index+1){
         setTimeout(function(){
            self.longFunction(index+1);
         },0);
      }
      else {
               //WORK FINISHED
      }

},
smallFunction : function(index){

// Do Stuff!

}

So here I am removing the loop and introducing a self calling function which increases its index each iteration. To return control to the main UI thread in order to prevent the javascript execution warning method I have added a setTimeout to allow it time to update after each iteration. The problem is that with this method getting the actual work done takes quite literally 10 times longer. What appears to be happening is although the setTimeout is set to 0, it is actually waiting more like 10ms. which on large arrays builds up very quickly. Removing the setTimeout and letting longFunction call itself gives performance comparable to the original loop method.

I need another solution, one which has comparable performance to the loop but which does not cause a javascript execution warning. Unfortunately webWorkers cannot be used in this instance.

It is important to note that I do not need a fully responsive UI during this process. Just enough to update a progress bar every few seconds.

Would breaking it up into chunks of loops be an option? I.e. perform 500 iterations at a time, stop, timeout, update progress bar, perform next 500 etc.. etc..

Is there anything better?

ANSWER:

The only solution seems to be chunking the work.

By adding the following to my self calling function I am allowing the UI to update every 250 iterations:

 longFunction : function(index){
           var self = this;
           var data = self.data;


          self.smallFunction(index);

          var nextindex = i+1;

          if(data.slides[nextindex){
            if(nextindex % 250 === 0){
             setTimeout(function(){               
                self.longFunction(nextindex);
             },0);
            }
            else {
                self.longFunction(nextindex);
            }
          }
          else {
                   //WORK FINISHED
          }

    },
    smallFunction : function(index){

    // Do Stuff!

    }

All I am doing here is checking if the next index is divisble by 250, if it is then we use a timeout to allow the main UI thread to update. If not we call it again directly. Problem solved!

like image 300
gordyr Avatar asked May 03 '13 06:05

gordyr


2 Answers

Here's some batching code modified from an earlier answer I had written:

var n = 0,
    max = data.length;
    batch = 100;

(function nextBatch() {
    for (var i = 0; i < batch && n < max; ++i, ++n) {
        myFunc(n);
    }
    if (n < max) {
        setTimeout(nextBatch, 0);
    }
})();
like image 37
Alnitak Avatar answered Nov 02 '22 20:11

Alnitak


Actually 1500 timeouts is nothing, so you can simply do this:

var i1 = 0
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(i1++) }, 0)

System will queue the timeout events for you and they will be called immediately one after another. And if users click anything during execution, they will not notice any lag. And no "script is running too long" thing.

From my experiments V8 can create 500,000 timeouts per second.

UPDATE

If you need i1 passed in order to your worker function, just pass an object with it, and increment the counter inside of your function.

function doSomething(obj) {
   obj.count++
   ...put actual code here
}
var obj = {count: 0}
for (var i = 0; i < 1500; i++) setTimeout(function() { doSomething(obj) }, 0)

Under Node.js you can aslo use setImmediate(...).

like image 170
exebook Avatar answered Nov 02 '22 20:11

exebook