2011-03-15 107 views
1

我在圖形庫(用於JavaScript/canvas,使用ImageData)中有一個簡單的框模糊函數。我可以爲這個功能做更多的優化嗎?

我已經做了一些優化,以避免成堆的冗餘代碼,如通過代碼複製[0..3]而不是複製代碼,並使每個周圍的像素用一個單一的未複製的代碼行實現,結束。

這些都是優化,以減少冗餘的代碼行。我可以做的更進一步的優化,或者更好的是,我可以改變的任何事情都可以改善功能本身的性能?

運行上的200x150圖像區域這一功能,與酷睿2,需要在Firefox 3.6約450ms,在Firefox 4 45ms和鉻約55毫秒10.

各種筆記

  • expressive.data.get返回一個ImageData對象
  • expressive.data.put一個ImageData的內容回寫到畫布
  • 一個ImageData是一個對象與:
    • unsigned long width
    • unsigned long height
    • Array data,格式r, g, b, a, r, g, b, a ...

代碼

expressive.boxBlur = function(canvas, x, y, w, h) { 
    // averaging r, g, b, a for now 
    var data = expressive.data.get(canvas, x, y, w, h); 
    for (var i = 0; i < w; i++) 
     for (var j = 0; j < h; j++) 
      for (var k = 0; k < 4; k++) { 
       var total = 0, values = 0, temp = 0; 
       if (!(i == 0 && j == 0)) { 
        temp = data.data[4 * w * (j - 1) + 4 * (i - 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(i == w - 1 && j == 0)) { 
        temp = data.data[4 * w * (j - 1) + 4 * (i + 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(i == 0 && j == h - 1)) { 
        temp = data.data[4 * w * (j + 1) + 4 * (i - 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(i == w - 1 && j == h - 1)) { 
        temp = data.data[4 * w * (j + 1) + 4 * (i + 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(j == 0)) { 
        temp = data.data[4 * w * (j - 1) + 4 * (i + 0) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(j == h - 1)) { 
        temp = data.data[4 * w * (j + 1) + 4 * (i + 0) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(i == 0)) { 
        temp = data.data[4 * w * (j + 0) + 4 * (i - 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       if (!(i == w - 1)) { 
        temp = data.data[4 * w * (j + 0) + 4 * (i + 1) + k]; 
        if (temp !== undefined) values++, total += temp; 
       } 
       values++, total += data.data[4 * w * j + 4 * i + k]; 
       total /= values; 
       data.data[4 * w * j + 4 * i + k] = total; 
      } 
    expressive.data.put(canvas, data, x, y); 
}; 
+0

我認爲你在定義臨時臨時使用它之前每個如果然後如果(臨時!== undefined)是不必要的 – Miquel 2011-03-15 15:45:48

+0

我檢查'臨時'是不是'未定義,因爲有一些情況下,我的邊緣/角落檢查方法沒有完全覆蓋,有時溫度未定義。我會重新測試,看看是否刪除檢查輸出一個不同的圖像。 – 2011-03-15 15:47:44

+0

啊,我明白了。那是我重寫函數時可能修復的一箇舊bug。沒有未定義的檢查輸出圖像是相同的。謝謝;我現在將其刪除。 – 2011-03-15 15:50:25

回答

1

可能(僅僅是可能)移動if檢查出儘可能將是一個優勢。讓我介紹一些僞代碼:

我就調用代碼遍歷k「內循環」爲簡單起見

// do a specialized version of "inner loop" that assumes i==0 
for (var i = 1; i < (w-1); i++) 
    // do a specialized version of "inner loop" that assumes j==0 && i != 0 && i != (w-1) 
    for (var j = 1; j < (h-1); j++) 
     // do a general version of "inner loop" that can assume i != 0 && j != 0 && i != (w-1) && j != (h-1) 
    } 
    // do a specialized version of "inner loop" that assumes j == (h - 1) && i != 0 && i != (w-1) 
} 
// do a specialized version of "inner loop" that assumes i == (w - 1) 

這將大大減少的數量,如果if檢查,因爲大多數操作將不需要它們。

+0

這將是我對最重大變化的賭注。 +1 – rsp 2011-03-15 16:17:34

1
一維數據

如果使用VAR數據的唯一方法是data.data則可以更改:

var data = expressive.data.get(canvas, x, y, w, h); 

到:

var data = expressive.data.get(canvas, x, y, w, h).data; 

,改變每一行,如:

temp = data.data[4 * w * (j - 1) + 4 * (i - 1) + k]; 

到:

temp = data[4 * w * (j - 1) + 4 * (i - 1) + k]; 

and you wil l保存一些名稱查找。

可能有更好的方法來優化它,但這正是我首先注意到的。

更新:

此外,if (i != 0 || j != 0)可以比if (!(i == 0 && j == 0))不僅是因爲否定的,但也因爲它可以做空cuircuit更快。

(使用=====!=進行自己的實驗對比!==因爲我的快速測試表明,似乎違反直覺給我的結果。)

,還有一些測試中完成了很多次,一些IFS是相互排斥的,但沒有一個else反正測試。你可以嘗試重構它有更多嵌套ifs和更多其他ifs。

+0

謝謝,我會那樣做的。 – 2011-03-15 15:38:05

+0

我已經承諾它現在git。在Firefox 4上使用測試頁時,似乎我可能會在每次迭代時削減5-10ms,所以它平均從大約45ms降至35-40ms。 – 2011-03-15 15:44:47

+0

很高興知道它速度更快。您還可以看到我對更多想法的答案的更新。 – rsp 2011-03-15 15:52:22

0

A小調優化:

var imgData = expressive.data.get(canvas, x, y, w, h); 
var data = imgData.data; 

// in your if statements 
temp = data[4 * w * (j - 1) + 4 * (i - 1) + k]; 

expressive.data.put(canvas, imgData, x, y) 

你也可以在你的指數進行了一些小的優化,例如:

4 * w * (j - 1) + 4 * (i - 1) + k // is equal to 
4 * ((w * (j-1) + (i-1)) + k 

var jmin1 = (w * (j-1)) 
var imin1 = (i-1) 
//etc, and then use those indices at the right place 

而且,把{}後,每對語句你有你碼。這2個字符不會有很大的區別。潛在的錯誤會。

+0

啊,這是個好主意。我的庫主要與'ImageData.data'數組接口,所以基本上每個函數都會執行索引算術('4 * ...')。我可以全面進行第二次優化,並節省一些處理時間。謝謝。 – 2011-03-15 15:46:34

0

你可以拉出來的一些常見表現:

for (var i = 0; i < w; i++) { 
    for (var j = 0; j < h; j++) { 
     var t = 4*w*j+4*i; 
     var dt = 4*j; 
     for (var k = 0; k < 4; k++) { 
      var total = 0, values = 0, temp = 0; 
      if (!(i == 0 && j == 0)) { 
       temp = data.data[t-dt-4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(i == w - 1 && j == 0)) { 
       temp = data.data[t-dt+4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(i == 0 && j == h - 1)) { 
       temp = data.data[t+dt-4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(i == w - 1 && j == h - 1)) { 
       temp = data.data[t+dt+4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(j == 0)) { 
       temp = data.data[t-dt+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(j == h - 1)) { 
       temp = data.data[t+dt+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(i == 0)) { 
       temp = data.data[t-4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      if (!(i == w - 1)) { 
       temp = data.data[t+4+k]; 
       if (temp !== undefined) values++, total += temp; 
      } 
      values++, total += data.data[t+k]; 
      total /= values; 
      data.data[t+k] = total; 
     } 
    } 
} 

你可以嘗試在k移動環路所以它的最外層,然後折+kt定義,節省更多的重複計算。 (這可能會因爲內存本地原因而變得很糟糕。)

您可以嘗試將j以上的循環移到i以外的循環之外,這將爲您提供更好的內存局部性。這對於大型圖像來說更重要。對於您使用的尺寸而言,它可能並不重要。

相當痛苦的,但可能很有效:你可以通過多達分割你的環插入{0,1..w-2,W-1}和{0,1..h-2,H損失了很多條件操作的-1}。

您可以擺脫所有undefined測試。考慮到你正在進行所有範圍檢查,你是否真的需要它們?

避免範圍檢查的另一種方法:您可以沿着每個邊緣將圖像(零點)填充一個像素。請注意,執行此操作的顯而易見的方法會在您的現有代碼的邊緣提供不同的結果;這可能是一件好事或壞事。如果這是一件壞事,你可以計算出合適的價值來劃分。

+0

我認爲用零填充圖像可能是件壞事,因爲這意味着完全透明的黑色邊緣,並且會變暗,或使邊緣像素平均更透明。目前的方法,只計算真實的周圍像素,平均「存在」的顏色。 – 2011-03-16 01:04:09

+0

這就是爲什麼我說「請注意,這樣做的顯而易見的方式將從現有代碼的邊緣給出不同的結果」,並指出它可以解決。 – 2011-03-16 08:40:28

0

temp = 0的聲明沒有必要,只需寫var total = 0, values = 0, temp;

接下來就是向後循環。

var length = 100, 
    i; 

for (i = 0; i < length; i++) {} 

慢於

var length = 100; 

for (; length != 0; length--) {} 

第三尖端是使用Duffy's Device爲巨大的for循環。