歡迎光臨
每天分享高質量文章

高效能JavaScript 達夫裝置

作者:韓子遲

網址:http://www.cnblogs.com/zichi/p/4661253.html

前言

在《高效能JavaScript》一書的第四章演演算法和流程控制中,提到了減少迭代次數加速程式的策略—達夫裝置(Duff’s device)。達夫裝置本身很好理解,但是其效果是否真的像書中所說“如果迭代次數超過1000,那麼達夫裝置的執行效率將明顯提升”?還是隨著瀏覽器效能的逐漸增強,這種以犧牲程式碼閱讀性而獲取的效能提升已經微不足道?

達夫裝置

達夫裝置真的很簡單,說白了就是“迴圈體展開”。看如下的程式碼:

var a = [0, 1, 2, 3, 4];

var sum = 0;

for(var i = 0; i < 5; i++)

sum += a[i];

console.log(sum);

我們將迴圈體展開來寫:

var a = [0, 1, 2, 3, 4];

var sum = 0;

sum += a[0];

sum += a[1];

sum += a[2];

sum += a[3];

sum += a[4];

console.log(sum);

因為少作了多次的for迴圈,很顯然這段程式碼比前者效率略高,而且隨著陣列長度的增加,少作的for迴圈將在時間上體現更多的優勢。

達夫裝置這種思想或者說是策略,原來是運用在C語言上的,Jeff Greenberg將它從C語言移植到了JavaScript上,我們可以來看看他寫的模板程式碼:

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);

註意看switch/case陳述句,因為沒有寫break,所以除了第一次外,之後的每次迭代實際上會執行8次!Duff’s Device背後的基本理念是:每次迴圈中最多可呼叫8次process()。迴圈的迭代次數為總數除以8。由於不是所有數字都能被8整除,變數startAt用來存放餘數,便是第一次迴圈中應呼叫多少次process()。

此演演算法一個稍快的版本取消了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陳述句,速度比原始迴圈更快。

效能測試

接著我們來進行達夫裝置的效能測試。如果迭代中的操作複雜的話,會減小達夫裝置最佳化對於時間的影響,所以迴圈內部我只選取了簡單的操作;而且為了方便操作,選取了8的倍數作為陣列長度。

var a = [];

var times = 1000;

// init array

for(var i = 1; i <= times; i++)

a[i] = i;

// ordinary way

console.time(‘1’);

var sum = 0;

for(var i = 1; i <= times; i++)

sum += 1 / a[i];

console.log(sum);

console.timeEnd(‘1’);

// Duff’s device

console.time(‘2’);

var sum = 0;

while(times) {

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

sum += 1 / a[times–];

}

console.log(sum);

console.timeEnd(‘2’);

當times取值較小時,得到了這樣的結果(chrome 版本 43.0.2357.134 m):

此時心中一萬頭草泥馬奔騰而過,默默問自己為什麼會出現這樣有悖常理的結果。直到times取值越來越大:

而在firefox(39.0)下則出現了更詭異的一幕,似乎達夫裝置對其不起任何效果:

那麼達夫裝置真的達不到想象當中的最佳化程度了嗎?為了驗證自己的猜想,同時在網上找到了一個外國朋友寫的測試程式碼JavaScript Loop Optimization,大多數的測試結果還是和預料一致,但是也能捕獲到這樣的截圖:

總結

經過測試,我覺得在迭代次數少的情況下,完全沒有必要用達夫裝置進行最佳化,且不說程式碼可讀性差,有時甚至會適得其反,而多大的迭代次數算多,多大算少,也不好說,特定的瀏覽器特定的版本都有其一定的取值。老版本的瀏覽器運用達夫裝置最佳化效能能得到大幅度的提升,而新版的瀏覽器引擎肯定對迴圈迭代陳述句進行了更強的最佳化,所以達夫裝置能實現的最佳化效果日趨減弱甚至於沒有。而在查閱資料的過程中,有人提出while迴圈的效果和達夫裝置不相上下,接下去也會針對不同的迴圈方式作進一步的探索。

贊(0)

分享創造快樂