讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議21:推薦提高循環性能的策略 >

建議21:推薦提高循環性能的策略

每次運行循環體時都會產生性能開銷,增加總的運行時間,即使是循環體中最快的代碼,累計迭代上千次,也將帶來不小的負擔。因此,減少循環的迭代次數可獲得顯著的性能提升。例如:


var iterations=Math.floor(items.length/8),startAt=items.length%8,i=0;

do{

switch(startAt){

case 0:

process(items[i++]);

case 7:

process(items[i++]);

case 6:

process(items[i++]);

case 5:

process(items[i++]);

case 4:

process(items[i++]);

case 3:

process(items[i++]);

case 2:

process(items[i++]);

case 1:

process(items[i++]);

}

startAt=0;

}while(--iterations);


在上面代碼中,每次循環最多可調用process函數8次。循環迭代次數為元素總數除以8。因為總數不一定是8的整數倍,所以startAt變量存放餘數,指明第一次循環中應當執行多少次process。如果現在有12個元素,那麼第一次循環將調用process4次,第二次循環調用process8次,用兩次循環代替了12次循環。在下面的代碼中取消switch表達式,將餘數處理與主循環分開。


var i=items.length%8;

while(i){

process(items[i--]);

}

i=Math.floor(items.length/8);

while(i){

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

}


雖然上面代碼使用兩個循環替代了先前的一個循環,但是在循環體中去掉了switch表達式,速度更快。如果循環的迭代次數少於1000次,減少迭代次數的循環與普通循環相比可能只有微不足道的性能提升。如果迭代次數超過1000次,比如在500 000次迭代中,合理地減少循環的迭代次數可以使運行時間減少到普通循環的70%。

有兩個因素影響到循環的性能:

❑每次迭代幹什麼。

❑迭代的次數。

通過減少這兩者中的一個或全部(的執行時間),可以提高循環的整體性能。如果一次循環需要較長時間來執行,那麼多次循環將需要更長時間。限制在循環體內進行耗時操作的數量是一個加快循環的好方法。一個典型的數組處理循環可採用3種循環的任何一種。最常用的代碼如下:


//方法1

for(var i=0;i<items.length;i++){

process(items[i]);

}

//方法2

var j=0;

while(j<items.length){

process(items[j++]);

}

//方法3

var k=0;

do{

process(items[k++]);

}while(k<items.length);


在每個循環中,每次運行循環體都要發生如下操作:

第1步,在控制條件中讀一次屬性(items.length)。

第2步,在控制條件中執行一次比較(i<items.length)。

第3步,比較操作,觀察條件控制體的運算結果是不是true(i<items.length==true)。

第4步,一次自加操作(i++)。

第5步,一次數組查找(items[i])。

第6步,一次函數調用(process(items[i]))。

在這些簡單的循環中,即使沒有太多的代碼,每次迭代都要進行這6步操作。代碼運行速度很大程度上由process對每個項目的操作所決定,即便如此,減少每次迭代中操作的總數也可以大幅度提高循環的整體性能。

優化循環的第一步是減少對像成員和數組項查找的次數。在大多數瀏覽器上,這些操作比訪問局部變量或直接量需要更長時間。例如,在上面代碼中,每次循環都查找items.length,這是一種浪費,因為該值在循環體執行過程中不會改變,因此產生了不必要的性能損失。我們可以簡單地將此值存入一個局部變量中,在控制條件中使用這個局部變量,從而提高了循環性能,例如:


for(var i=0,len=items.length;i<len;i++){

process(items[i]);

}

var j=0,count=items.length;

while(j<count){

process(items[j++]);

}

var k=0,num=items.length;

do{

process(items[k++]);

}while(k<num);


這些重寫後的循環只在循環執行之前對數組長度進行一次屬性查詢,使控制條件中只有局部變量參與運算,因此速度更快。根據數組的長度,在大多數瀏覽器上總循環時間可以節省大約25%,在IE瀏覽器中可節省50%。

還可以通過改變循環的順序來提高循環性能。通常,數組元素的處理順序與任務無關,可以從最後一個開始,直到處理完第一個元素。倒序循環是編程語言中常用的性能優化方法,不過一般不太容易理解。在JavaScript中,倒序循環可以略微提高循環性能,例如:


for(var i=items.length;i--;){

process(items[i]);

}

var j=items.length;

while(j--){

process(items[j]);

}

var k=items.length-1;

do{

process(items[k]);

}while(k--);


在上面代碼中使用了倒序循環,並且在控制條件中使用了減法。每個控制條件只是簡單地與零進行比較。控制條件與true值進行比較,任何非零數字自動強制轉換為true,而零等同於false。實際上,控制條件已經從兩次比較減少到一次比較。將每次迭代中兩次比較減少到一次可以大幅度提高循環速度。通過倒序循環和最小化屬性查詢,可以看到執行速度比原始版本提升了50%~60%。

與原始版本相比,每次迭代中只進行如下操作:

第1步,在控制條件中進行一次比較(i==true)。

第2步,一次減法操作(i--)。

第3步,一次數組查詢(items[i])。

第4步,一次函數調用(process(items[i]))。

新循環的每次迭代中減少兩個操作,隨著迭代次數的增長,性能將顯著提升。