Javascript各種循環測試,達夫設備可用性分析

? 《High Performance JavaScript》一書對於JavaScript中的如何提高循環的性能介紹的十分詳盡,自己寫了些簡單的代碼進行了測試,測試結果竟然出乎意料。

? 首先需要說明的是,本次測試的瀏覽器版本爲Mozilla Firefox V7.01、基於chrome內核的傲遊V3.1.8.1000,至於IE8那個廢材我實在無語(系統是XP,IE9裝不了無法測試)。

? 書中的主要觀點主要有下面幾個:

? 1、除 for-in循環外,其他循環類型性能相當,難以確定哪種循環更快。選擇循環類型應基於需求而不是性能。

? 2、減少迭代的工作量,包括減少對象成員和數組項查找的次數、如果數組元素的處理順序與任務無關使用倒序循環可以略微提高循環性能。

? 3、減少迭代次數,介紹了最廣爲人知的限制循環迭代次數的模式稱作“達夫設備”。

 

? 倒序循環

? 首先出問題的是倒序循環,一般在正常的循環中,每次運行循環體都要發生如下幾個操作:

? 1.在控制條件中讀一次屬性(items.length),如果緩存了數組的長度,這一條可忽略

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

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

? 4. 一次自加操作(i++)

? 5. 一次數組查找(items[i])

? 6. 一次函數調用(process(items[i]))

? 在倒序循環中,每次迭代中只進行如下操作:

? 1.在控制條件中進行一次比較(i == true)

? 2.一次減法操作(i--)

? 3.一次數組查詢(items[i])

? 4.一次函數調用(process(items[i]))

? 倒序循環減少了操作,那麼性能的提高雖說很微弱,但應該是百分百確定的對不?但是我測試的結果卻不盡然,簡單說一下測試代碼,首先創建一個長度爲10000的數組,每一項爲長度4-10的字符,每個循環體要進行的操作是判斷字符串是否爲全數字,如果是,值累加1,最後每種循環都循環100次,做三次測試。先上代碼:

  1. var chars = '0123456789abcdef'
  2.  
  3. function getRandomString() { 
  4.     var len = Math.ceil(Math.random() * 7) + 3; // 4-10 
  5.     var result = ""
  6.     while (len--) { 
  7.         result += chars.charAt(Math.floor(Math.random() * chars.length)); 
  8.     } 
  9.     return result; 
  10.  
  11. var size = 10000; 
  12. var mult = 100; 
  13.  
  14. var ary = []; 
  15. var lsize = size; 
  16. while (lsize--) { ary.push('' + getRandomString() + ''); } 
  17.  
  18. function for_in() { 
  19.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  20.     for (var i in ary) { 
  21.         var item = i; 
  22.         if (isInt.test(item)) { item += 1; } 
  23.     } 
  24.  
  25. function for_normal() { 
  26.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  27.     for (var i = 0, len = ary.length; i < len; i++) { 
  28.         var item = ary[i]; 
  29.         if (isInt.test(item)) { item += 1; } 
  30.     } 
  31.  
  32. function for_reverse() { 
  33.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  34.     for (var i = ary.length; i--; ) { 
  35.         var item = ary[i]; 
  36.         if (isInt.test(item)) { item += 1; } 
  37.     } 
  38.  
  39. function while_normal() { 
  40.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  41.             i = 0, 
  42.             l = ary.length; 
  43.     while (i < l) { 
  44.         var item = ary[i++]; 
  45.         if (isInt.test(item)) { item += 1; } 
  46.     } 
  47.  
  48. function while_reverse() { 
  49.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  50.             i = ary.length; 
  51.     while (i--) { 
  52.         var item = ary[i]; 
  53.         if (isInt.test(item)) { item += 1; } 
  54.     } 
  55.  
  56. function do_while_normal() { 
  57.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  58.             i = 0, 
  59.             l = ary.length; 
  60.     do { 
  61.         var item = ary[i++]; 
  62.         if (isInt.test(item)) { item += 1; } 
  63.     } while (i < l) 
  64.  
  65. function do_while_reverse() { 
  66.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  67.             i = ary.length - 1; 
  68.     do { 
  69.         var item = ary[i]; 
  70.         if (isInt.test(item)) { item += 1; } 
  71.     } while (i--) 
  72.  
  73. console.log('數組長度:' + ary.length); 
  74. console.log('Profiling will begin in 2 seconds...'); 
  75. setTimeout(function () { 
  76.     var currTime; 
  77.     for (var k = 0; k < 3; k++) { 
  78.         console.log('第' + (k + 1) + '次循環測試:'
  79.         currTime = new Date(); 
  80.         console.profile(); 
  81.         for (var i = 0; i < mult; i++) { 
  82.             for_in(); 
  83.             for_normal(); 
  84.             for_reverse(); 
  85.             while_normal(); 
  86.             while_reverse(); 
  87.             do_while_normal(); 
  88.             do_while_reverse(); 
  89.         } 
  90.         console.profileEnd(); 
  91.         console.log('用時:' + (new Date() - currTime) + 'ms'); 
  92.     } 
  93. }, 2000); 
? 測試n次後發現,while、do-while都要比for循環要快些,for尋找只是偶爾比前兩個快,在火狐下do-while要被while快些,在chrome下不明顯。
? fox循環的倒序循環性能略微提升比較確定,但是不管在火狐還是在chrome下while、do-while的正循環都要快於倒序循環,這是咋回事?別問我,我也不知......

? 再上測試的截屏,這裏不得不讚Firebug,它的profile可不chrome強多了,不過性能還是比不上chrome,尤其後面達夫設備的測試跟是明顯。
 

 

? 在達夫設備測試之前先進行另外一項測試,一上面的for循環爲例,如果把循環體內對數組項的操作單獨另寫一個函數,然後在循環內調用,性能會有什麼樣的影響呢?

  1. var chars = '0123456789abcdef'
  2.  
  3. function getRandomString() { 
  4.     var len = Math.ceil(Math.random() * 7) + 3; // 4-10 
  5.     var result = ""
  6.     while (len--) { 
  7.         result += chars.charAt(Math.floor(Math.random() * chars.length)); 
  8.     } 
  9.     return result; 
  10.  
  11. var size = 10000; 
  12. var mult = 100; 
  13.  
  14. var ary = []; 
  15. var lsize = size; 
  16. while (lsize--) { ary.push('' + getRandomString() + ''); } 
  17.  
  18. function process(item) { 
  19.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  20.     if (isInt.test(item)) { item += 1; } 
  21.  
  22. function for_normal_fn() { 
  23.     for (var i = 0, len = ary.length; i < len; i++) { process(ary[i]); } 
  24.  
  25. function for_normal() { 
  26.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  27.     for (var i = 0, len = ary.length; i < len; i++) { 
  28.         var item = ary[i]; 
  29.         if (isInt.test(item)) { item += 1; } 
  30.     } 
  31.  
  32. console.log('數組長度:' + ary.length); 
  33. console.log('Profiling will begin in 2 seconds...'); 
  34. setTimeout(function () { 
  35.     console.log('普通for循環測試:'
  36.     var currTime = new Date(); 
  37.     console.profile(); 
  38.     for (var i = 0; i < mult; i++) { 
  39.         for_normal(); 
  40.     } 
  41.     console.profileEnd(); 
  42.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  43.  
  44.     console.log('for循環內調用外部函數測試:'
  45.     currTime = new Date(); 
  46.     console.profile(); 
  47.     for (var i = 0; i < mult; i++) { 
  48.         for_normal_fn(); 
  49.     } 
  50.     console.profileEnd(); 
  51.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  52.  
  53. }, 2000); 

? 測試的結果:調用外部函數的for循環比之前的for循環所用的時間,火狐下多了二十倍,chrome沒那麼誇張,也多用的50ms左右,同是瀏覽器,差距咋就這麼大呢(火狐你也要堅強些,因爲IE在這測試裏連說話的資格也沒有,哈哈)......

 
 
? 達夫設備
 
? 這裏摘抄一下書中關於達夫設備的基本理念:每次循環中最多可 8 次調用 process()函數。循環迭代次數爲元素總數除以8。 因爲總數不一定是 8的整數倍, 所以 startAt 變量存放餘數, 指出第一次循環中應當執行多少次 process()。比方說現在有 12 個元素,那麼第一次循環將調用 process()4次,第二次循環調用 process()8 次,用 2 次循環代替了 12次循環。
 
? 下面我們比較一下for、while、Duff's Device三種循環:
 
  1. var chars = '0123456789abcdef'
  2.  
  3. function getRandomString() { 
  4.     var len = Math.ceil(Math.random() * 7) + 3; // 4-10 
  5.     var result = ""
  6.     while (len--) { 
  7.         result += chars.charAt(Math.floor(Math.random() * chars.length)); 
  8.     } 
  9.     return result; 
  10.  
  11. var size = 10000; 
  12. var mult = 100; 
  13.  
  14. var ary = []; 
  15. var lsize = size; 
  16. while (lsize--) { ary.push('' + getRandomString() + ''); } 
  17.  
  18. function for_normal() { 
  19.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  20.     for (var i = 0, len = ary.length; i < len; i++) { 
  21.         var item = ary[i]; 
  22.         if (isInt.test(item)) { item += 1; } 
  23.     } 
  24.  
  25. function while_normal() { 
  26.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  27.             i = 0, 
  28.             l = ary.length; 
  29.     while (i < l) { 
  30.         var item = ary[i++]; 
  31.         if (isInt.test(item)) { item += 1; } 
  32.     } 
  33.  
  34. function duff_fast() { 
  35.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  36.             item, i = 0, 
  37.             l = ary.length, 
  38.             n = l % 8; 
  39.     while (n--) { 
  40.         item = ary[i++]; 
  41.         if (isInt.test(item)) { item += 1; } 
  42.     } 
  43.  
  44.     n = parseInt(l / 8); 
  45.     while (n--) { 
  46.         item = ary[i++]; 
  47.         if (isInt.test(item)) { item += 1; } 
  48.         item = ary[i++]; 
  49.         if (isInt.test(item)) { item += 1; } 
  50.         item = ary[i++]; 
  51.         if (isInt.test(item)) { item += 1; } 
  52.         item = ary[i++]; 
  53.         if (isInt.test(item)) { item += 1; } 
  54.         item = ary[i++]; 
  55.         if (isInt.test(item)) { item += 1; } 
  56.         item = ary[i++]; 
  57.         if (isInt.test(item)) { item += 1; } 
  58.         item = ary[i++]; 
  59.         if (isInt.test(item)) { item += 1; } 
  60.         item = ary[i++]; 
  61.         if (isInt.test(item)) { item += 1; } 
  62.     } 
  63.  
  64. console.log('數組長度:' + ary.length); 
  65. console.log('Profiling will begin in 2 seconds...'); 
  66. setTimeout(function () { 
  67.     console.log('普通for循環測試:'
  68.     var currTime = new Date(); 
  69.     console.profile(); 
  70.     for (var i = 0; i < mult; i++) { 
  71.         for_normal(); 
  72.     } 
  73.     console.profileEnd(); 
  74.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  75.  
  76.     console.log('普通while循環測試:'
  77.     var currTime = new Date(); 
  78.     console.profile(); 
  79.     for (var i = 0; i < mult; i++) { 
  80.         while_normal(); 
  81.     } 
  82.     console.profileEnd(); 
  83.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  84.  
  85.     console.log('達夫設備循環測試:'
  86.     currTime = new Date(); 
  87.     console.profile(); 
  88.     for (var i = 0; i < mult; i++) { 
  89.         duff_fast(); 
  90.     } 
  91.     console.profileEnd(); 
  92.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  93. }, 2000); 

? 經過n次測試,發現while、Duff's Device要比for要快,而while與Duff's Device竟然也不分伯仲啊,這、這、這.....while咋就這麼強呢......

? 還有一點達夫設備實際上把八次迭代合在一次迭代中,如果每次對數組項的操作代碼量較少的情況下你可以像上面的代碼一樣簡單的複製八次,但是如果操作量大一些的話爲了便於閱讀和調試源代碼,你就必須把操作代碼包裝到另外一個process函數中,然後在達夫設備調用此函數,還記得我們前面的關於for循環調用外部函數的測試嗎?性能就會差太多了,可以看下面的測試:

  1. var chars = '0123456789abcdef'
  2.  
  3. function getRandomString() { 
  4.     var len = Math.ceil(Math.random() * 7) + 3; // 4-10 
  5.     var result = ""
  6.     while (len--) { 
  7.         result += chars.charAt(Math.floor(Math.random() * chars.length)); 
  8.     } 
  9.     return result; 
  10.  
  11. var size = 10000; 
  12. var mult = 100; 
  13.  
  14. var ary = []; 
  15. var lsize = size; 
  16. while (lsize--) { ary.push('' + getRandomString() + ''); } 
  17.  
  18. function process(item) { 
  19.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  20.     if (isInt.test(item)) { item += 1; } 
  21.  
  22. function for_normal() { 
  23.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/; 
  24.     for (var i = 0, len = ary.length; i < len; i++) { 
  25.         var item = ary[i]; 
  26.         if (isInt.test(item)) { item += 1; } 
  27.     } 
  28.  
  29. function while_normal() { 
  30.     var isInt = /(^[0-9]$)|(^[1-9][0-9]+$)/, 
  31.             i = 0, 
  32.             l = ary.length; 
  33.     while (i < l) { 
  34.         var item = ary[i++]; 
  35.         if (isInt.test(item)) { item += 1; } 
  36.     } 
  37.  
  38. function duff_normal() { 
  39.     var i = 0, 
  40.             l = ary.length, 
  41.             n = l % 8; 
  42.     while (n--) { 
  43.         process(ary[i++]); 
  44.     } 
  45.  
  46.     n = parseInt(l / 8); 
  47.     while (n--) { 
  48.         process(ary[i++]); 
  49.         process(ary[i++]); 
  50.         process(ary[i++]); 
  51.         process(ary[i++]); 
  52.         process(ary[i++]); 
  53.         process(ary[i++]); 
  54.         process(ary[i++]); 
  55.         process(ary[i++]); 
  56.     } 
  57.  
  58. console.log('數組長度:' + ary.length); 
  59. console.log('Profiling will begin in 2 seconds...'); 
  60. setTimeout(function () { 
  61.     console.log('普通for循環測試:'
  62.     var currTime = new Date(); 
  63.     console.profile(); 
  64.     for (var i = 0; i < mult; i++) { 
  65.         for_normal(); 
  66.     } 
  67.     console.profileEnd(); 
  68.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  69.  
  70.     console.log('普通while循環測試:'
  71.     var currTime = new Date(); 
  72.     console.profile(); 
  73.     for (var i = 0; i < mult; i++) { 
  74.         while_normal(); 
  75.     } 
  76.     console.profileEnd(); 
  77.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  78.  
  79.     console.log('達夫設備調用外部函數循環測試:'
  80.     currTime = new Date(); 
  81.     console.profile(); 
  82.     for (var i = 0; i < mult; i++) { 
  83.         duff_normal(); 
  84.     } 
  85.     console.profileEnd(); 
  86.     console.log('用時:' + (new Date() - currTime) + 'ms'); 
  87.  
  88. }, 2000); 

? 看到測試結果了吧,慘不忍睹啊......還是用while、do-while循環吧......

? 苦苦的苦瓜 2011-10-10
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章