2012-03-19 21 views
11

処理に時間がかかるjavascriptループがあります。私はそれをスリムにすることができますが、それは大量のデータを処理する必要があります。それが実行されている間、ブラウザはもちろん無応答になります。私はjavascriptでこれを処理する最善の方法をいくつかの並べ替えの非同期ループを使用してお読みください。このように、マウスクリックなどは、ループ処理の間に処理され続けることができます。これに対してうまくいく標準的な非同期フレームワークはありますか?あるいは、誰かがこれをコード化する方法の簡単な例を提供することができますか?ありがとう!JavaScriptの非同期ループ処理

+0

可能重複http://stackoverflow.com/questions/4288759/asynchronous-for-cycle-in-javascript) –

+0

IEと互換性がありますか?そうでない場合は、私はウェブワーカー[mozilla tutorial](https://developer.mozilla.org/En/Using_web_workers)を探してみることをお勧めします –

+0

残念ながら、それはIE8で動作する必要があります。 – skinneejoe

答えて

7

悲しいことに、WebWorkersは誰のブラウザでも利用できません。私は "setTimeout(Func、0);を使用しています。約1年間トリック。ここで私はそれを少し速くする方法を説明するために書いたいくつかの最近の研究です。答えがほしいのであれば、ステップ4に進んでください。ステップ1と2は、推論とメカニズムを説明しています。

// In Depth Analysis of the setTimeout(Func,0) trick. 

//////// setTimeout(Func,0) Step 1 //////////// 
// setTimeout and setInterval impose a minimum 
// time limit of about 2 to 10 milliseconds. 

    console.log("start"); 
    var workCounter=0; 
    var WorkHard = function() 
    { 
    if(workCounter>=2000) {console.log("done"); return;} 
    workCounter++; 
    setTimeout(WorkHard,0); 
    }; 

// this take about 9 seconds 
// that works out to be about 4.5ms per iteration 
// Now there is a subtle rule here that you can tweak 
// This minimum is counted from the time the setTimeout was executed. 
// THEREFORE: 

    console.log("start"); 
    var workCounter=0; 
    var WorkHard = function() 
    { 
    if(workCounter>=2000) {console.log("done"); return;} 
    setTimeout(WorkHard,0); 
    workCounter++; 
    }; 

// This code is slightly faster because we register the setTimeout 
// a line of code earlier. Actually, the speed difference is immesurable 
// in this case, but the concept is true. Step 2 shows a measurable example. 
/////////////////////////////////////////////// 


//////// setTimeout(Func,0) Step 2 //////////// 
// Here is a measurable example of the concept covered in Step 1. 

    var StartWork = function() 
    { 
    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var sum=0; 
    var WorkHard = function() 
    { 
     if(workCounter>=2000) 
     { 
     var ms = (new Date()).getTime() - startTime.getTime(); 
     console.log("done: sum=" + sum + " time=" + ms + "ms"); 
     return; 
     } 
     for(var i=0; i<1500000; i++) {sum++;} 
     workCounter++; 
     setTimeout(WorkHard,0); 
    }; 
    WorkHard(); 
    }; 

// This adds some difficulty to the work instead of just incrementing a number 
// This prints "done: sum=3000000000 time=18809ms". 
// So it took 18.8 seconds. 

    var StartWork = function() 
    { 
    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var sum=0; 
    var WorkHard = function() 
    { 
     if(workCounter>=2000) 
     { 
     var ms = (new Date()).getTime() - startTime.getTime(); 
     console.log("done: sum=" + sum + " time=" + ms + "ms"); 
     return; 
     } 
     setTimeout(WorkHard,0); 
     for(var i=0; i<1500000; i++) {sum++;} 
     workCounter++; 
    }; 
    WorkHard(); 
    }; 

// Now, as we planned, we move the setTimeout to before the difficult part 
// This prints: "done: sum=3000000000 time=12680ms" 
// So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms 
// We have effectively shaved off 3.1ms of the original 4.5ms of dead time. 
// Assuming some of that time may be attributed to function calls and variable 
// instantiations, we have eliminated the wait time imposed by setTimeout. 

// LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high 
// performance in mind, make sure your function takes more than 4.5ms, and set 
// the next timeout at the start of your function, instead of the end. 
/////////////////////////////////////////////// 


//////// setTimeout(Func,0) Step 3 //////////// 
// The results of Step 2 are very educational, but it doesn't really tell us how to apply the 
// concept to the real world. Step 2 says "make sure your function takes more than 4.5ms". 
// No one makes functions that take 4.5ms. Functions either take a few microseconds, 
// or several seconds, or several minutes. This magic 4.5ms is unattainable. 

// To solve the problem, we introduce the concept of "Burn Time". 
// Lets assume that you can break up your difficult function into pieces that take 
// a few milliseconds or less to complete. Then the concept of Burn Time says, 
// "crunch several of the individual pieces until we reach 4.5ms, then exit" 

// Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality 
// we could have easilly incremented workCounter 2000 times in under a millisecond. 
// So, duh, that should not be made asyncronous, its horrible. But what if you don't know 
// how many times you need to increment the number, maybe you need to run the loop 20 times, 
// maybe you need to run the loop 2 billion times. 

    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    for(var i=0; i<2000000000; i++) // 2 billion 
    { 
    workCounter++; 
    } 
    var ms = (new Date()).getTime() - startTime.getTime(); 
    console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// prints: "done: workCounter=2000000000 time=7214ms" 
// So it took 7.2 seconds. Can we break this up into smaller pieces? Yes. 
// I know, this is a retarded example, bear with me. 

    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var each = function() 
    { 
    workCounter++; 
    }; 
    for(var i=0; i<20000000; i++) // 20 million 
    { 
    each(); 
    } 
    var ms = (new Date()).getTime() - startTime.getTime(); 
    console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// The easiest way is to break it up into 2 billion smaller pieces, each of which take 
// only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion 
// to 20 million (100x less). Just adding a function call increases the complexity of the loop 
// 100 fold. Good lesson for some other topic. 
// prints: "done: workCounter=20000000 time=7648ms" 
// So it took 7.6 seconds, thats a good starting point. 
// Now, lets sprinkle in the async part with the burn concept 

    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var index=0; 
    var end = 20000000; 
    var each = function() 
    { 
    workCounter++; 
    }; 
    var Work = function() 
    { 
    var burnTimeout = new Date(); 
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future 
    while((new Date()) < burnTimeout) 
    { 
     if(index>=end) 
     { 
     var ms = (new Date()).getTime() - startTime.getTime(); 
     console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
     return; 
     } 
     each(); 
     index++; 
    } 
    setTimeout(Work,0); 
    }; 

// prints "done: workCounter=20000000 time=107119ms" 
// Sweet Jesus, I increased my 7.6 second function to 107.1 seconds. 
// But it does prevent the browser from locking up, So i guess thats a plus. 
// Again, the actual objective here is just to increment workCounter, so the overhead of all 
// the async garbage is huge in comparison. 
// Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part. 

    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var index=0; 
    var end = 20000000; 
    var each = function() 
    { 
    workCounter++; 
    }; 
    var Work = function() 
    { 
    if(index>=end) {return;} 
    setTimeout(Work,0); 
    var burnTimeout = new Date(); 
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future 
    while((new Date()) < burnTimeout) 
    { 
     if(index>=end) 
     { 
     var ms = (new Date()).getTime() - startTime.getTime(); 
     console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
     return; 
     } 
     each(); 
     index++; 
    } 
    }; 

// This means we also have to check index right away because the last iteration will have nothing to do 
// prints "done: workCounter=20000000 time=52892ms" 
// So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds. 
// The Burn Time is the number you tweak to get a nice balance between native loop speed 
// and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster 
// than 50ms gui response. 

    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var index=0; 
    var end = 20000000; 
    var each = function() 
    { 
    workCounter++; 
    }; 
    var Work = function() 
    { 
    if(index>=end) {return;} 
    setTimeout(Work,0); 
    var burnTimeout = new Date(); 
    burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future 
    while((new Date()) < burnTimeout) 
    { 
     if(index>=end) 
     { 
     var ms = (new Date()).getTime() - startTime.getTime(); 
     console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
     return; 
     } 
     each(); 
     index++; 
    } 
    }; 

// prints "done: workCounter=20000000 time=52272ms" 
// So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout 
// have been eliminated as long as the burn time is anything over 4.5ms 
/////////////////////////////////////////////// 


//////// setTimeout(Func,0) Step 4 //////////// 
// The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it. 
// Here is a short library that embodies these concepts and gives a descent interface. 

    var WilkesAsyncBurn = function() 
    { 
    var Now = function() {return (new Date());}; 
    var CreateFutureDate = function(milliseconds) 
    { 
     var t = Now(); 
     t.setTime(t.getTime() + milliseconds); 
     return t; 
    }; 
    var For = function(start, end, eachCallback, finalCallback, msBurnTime) 
    { 
     var i = start; 
     var Each = function() 
     { 
     if(i==-1) {return;} //always does one last each with nothing to do 
     setTimeout(Each,0); 
     var burnTimeout = CreateFutureDate(msBurnTime); 
     while(Now() < burnTimeout) 
     { 
      if(i>=end) {i=-1; finalCallback(); return;} 
      eachCallback(i); 
      i++; 
     } 
     }; 
     Each(); 
    }; 
    var ForEach = function(array, eachCallback, finalCallback, msBurnTime) 
    { 
     var i = 0; 
     var len = array.length; 
     var Each = function() 
     { 
     if(i==-1) {return;} 
     setTimeout(Each,0); 
     var burnTimeout = CreateFutureDate(msBurnTime); 
     while(Now() < burnTimeout) 
     { 
      if(i>=len) {i=-1; finalCallback(array); return;} 
      eachCallback(i, array[i]); 
      i++; 
     } 
     }; 
     Each(); 
    }; 

    var pub = {}; 
    pub.For = For;   //eachCallback(index); finalCallback(); 
    pub.ForEach = ForEach; //eachCallback(index,value); finalCallback(array); 
    WilkesAsyncBurn = pub; 
    }; 

/////////////////////////////////////////////// 


//////// setTimeout(Func,0) Step 5 //////////// 
// Here is an examples of how to use the library from Step 4. 

    WilkesAsyncBurn(); // Init the library 
    console.log("start"); 
    var startTime = new Date(); 
    var workCounter=0; 
    var FuncEach = function() 
    { 
    if(workCounter%1000==0) 
    { 
     var s = "<div></div>"; 
     var div = jQuery("*[class~=r1]"); 
     div.append(s); 
    } 
    workCounter++; 
    }; 
    var FuncFinal = function() 
    { 
    var ms = (new Date()).getTime() - startTime.getTime(); 
    console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
    }; 
    WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50); 

// prints: "done: workCounter=20000000 time=149303ms" 
// Also appends a few thousand divs to the html page, about 20 at a time. 
// The browser is responsive the entire time, mission accomplished 

// LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through 
// an array summing the numbers, then just putting it in an "each" function is going to kill you. 
// You can still use the concept here, but your "each" function should also have a for loop in it 
// where you burn a few hundred items manually. 
/////////////////////////////////////////////// 
あなたは単純にそうよう setTimeoutループの各反復をラップすることができます
+0

これは非常に教育的でした!私がこの問題を抱えていたときに取り組んでいたプロジェクトは、過去にうまくいきました。私はそれをどうやって解決したのか覚えていませんが、これは確かに面白い読書です。私はそれが特定の状況で役立つのを見ることができました。 – skinneejoe

2

作業をチャンクに分割し、一度に1つのチャンクを処理するだけです。コードhereは良い開始場所ですが、ループの次の繰り返しを呼び出すにはsetImmediateまたはsetTimeoutを使用してください。

あなたの問題を解決するには、コードを別のスレッドで実行するWeb Workersを使用します。

+0

はいWebワーカーは素晴らしいですが、私はIE8をサポートする必要があります。 – skinneejoe

+0

両方の世界のベスト:仕事をチャンクに分割し、利用可能であればワーカースレッドでチャンクを実行します。それ以外の場合はチャンクを正常に実行します。 – josh3736

+0

ループをチャンクに分割する方法がわかりません。私はjavscriptの.sort関数を使用しています。何か案は? – skinneejoe

1

jsfiddleを参照してください):

$(document).ready(function(){ 
    var COUNT = 100000; 
    function process(item){ 
     var r = 0; 
     for(var i=0; i < item; i++){ 
      r += i; 
     } 
     return r; 
    } 
    for(var i=0; i < COUNT; i++){ 
     (function(item){ 
      setTimeout(function(){ 
       $('#log').html("Processing #" + item + " (" + process(item) + ")"); 
      }); 
     })(i); 
    } 
}); 
[JavaScriptでのサイクルのための非同期](の