2012/9/2

JavaScript 字串處理效能分析

作為一個前端語言, JavaScript 天生就註定要比其它任何一種語言要處理更多的字串。如何才能更有效地處理字串, 想必也是每個程式設計師最關心的問題。最近, 我看到有人很堅持地在程式中把個別的字串以陣列元素方式宣告, 然後把它們 join 起來, 說這樣才能達到最佳的效能, 其速度遠比使用字串的加號運算來得快! 起初我也半信半疑, 直到我寫了一個程式來仔細分析為止。

如果你沒有耐心的話, 我可以先把我的結論寫出來。如果你對我的分析過程有興趣, 可以再往下閱讀。

我的結論就是, 使用陣列的 join() 並不一定比使用字串的加號運算來得快; 二者的運算速度坦白說差異並沒有很大, 而且跟瀏覽器有絕對的關係。

理論

這裡有一篇很詳細的文章 "String Performance: an Analysis", 把 JavaScript 如何處理字串從理論到實際談了一遍。不過, 很可惜的, 這篇文章是在四年多前發表的, 日換星移, 很多事情已經隨著時間而改變了。

在我的看法中, 既然字串處理在 JavaScript 中有舉足輕重的地位, 想必各家瀏覽器會特別為字串處理進行效能調校, 所以理論歸理論, 實際上恐怕什麼理論都不一定準確。

其實不光是 JavaScript, 不管什麼語言, 字串始終是一種最特別的型別, 不能單純的拿來與其它 primitive types 相提並論。在我後面的分析中, 各位應該也可以看出一點端倪。

程式

我把我的測試程式列在下面。在程式中, 我首先建立一個內含若干個字串元素的陣列, 然後以加號運算把這些元素合併成一個字串, 再改用 string.concat(), 再改用 array.join(), 得到以上三種方法所耗費的時間。此外, 基於好奇, 我也把產生受測陣列的時間也一併算出來, 列在第一個; 所以總共有四個時間間隔。不過我們實際上只看後面三個數字。

此外, 由於 JavaScript 並沒有提供 StopWatch 工具, 所以我採用 MCF.Goodwin 的 JavaScript Stopwatch 來作為計時工具。

分析

我選擇了四種瀏覽器來比較其結果, 分別是 Chrome 21.0.1180.83m, FireFox 15.0, IE9 (on Windows 7) 和 IE10 RC (on Windows 8 RC)。

照理說, 字串的加號運算和 string.concat() 應該有相同的效率, 而結果也大致如此。

倒是字串的加號運算和陣列的 join() 運算, 在各大瀏覽器上面就可以看出比較具體的差異了。基本上, 使用陣列的 join() 來合併字串, 果然比使用字串的加號運算, 可以節省大約一半的時間。所以若要將字串合併, 改用陣列的 join() 運算來做, 確實是比較快的。

不過, 這個結論遇到 FireFox 就不成立了。從實際數據來看, 如果你的瀏覽器是 FireFox, 那麼使用字串加號運算幾乎都比使用陣列的 join() 運算來得快。至於 Chrome, 如果子字串的數目不多, 使用 join() 確實比較快, 但是一旦子字串的數目多到一定程度, 就變成反而是使用字串的加號運算比較來得有效率了。

此外, 值得一提的是, FireFox 在各項測試中的效率和穩定度都是最好的; 它也是唯一能在子字串數量高達兩千萬個時, 仍然能傳回執行結果 (四個結果中成功三個) 的。而且 FireFox 的字串處理效能十分驚人, 在所有測試中都高居第一名, 在處理高達兩千萬個子字串相加時, 其速度都不到一秒 (我覺得它應該是特別為了字串處理做了優化, 其內部應該是採用 String Builder)。它在產生龐大數量的陣列時, 執行時間也都能維持著 O(n)。

在處理大量資料方面, 無論是 IE9 或是 IE10, 其測試結果都不太理想。雖然 IE10 的效率相較於 IE9 時可以看到長足的進步, 但是一旦子字串數目超過一千萬筆, 這兩兄弟就當掉了。不過在它們當掉之前, 我看到瀏覽器曾經出現無回應的警告, 所以也有可能是因為瀏覽器內部啟動了除錯機制而導致指令沒有繼續進行, 不一定是 JavaScript 引擎當掉。

我覺得我不想拿這種極限測試的結果來臧否不同瀏覽器的好壞, 畢竟這不僅是效能問題, 它還牽涉到程式記憶體配置的問題。在前端程式中, 就任何目前想像得到的場景中, 要把一千萬個子字串相加的機會應該是沒有的。換句話說, 什麼瀏覽器能撐到一千萬筆, 什麼瀏覽器能撐到兩千萬筆, 恐怕並不是那麼重要, 不是嗎?

那麼, 回到文章的主題, 到底我們需不需要使用陣列的合併來取代字串的合併呢? 我的看法是這樣的: 除非你拿 JavaScript 來寫後端程式, 而且運用到極為複雜的反覆運算, 否則你實在可以不用在乎這食如雞肋的一小丁點效能差異。就拿表現最差的 IE9 來舉例吧! 在高達五百萬個字串合併的運算過程中, 使用加號和使用陣列的 join() 運算, 花費的時都差不到兩秒! 你到底都在寫些什麼偉大的程式, 隨時會動用到幾百萬筆字串的相加, 使得你會開始在乎這兩秒鐘? 如果沒有, 那麼, 還是繼續使用你習慣的做法吧!

數據

以下我把我得到的測試數據列在下面, 僅供參考。

JavaScript 字串處理效能分析     單位: 秒
             
程式: http://jsfiddle.net/RUPBm      
             
瀏覽器 Chrome 版本: 21.0.1180.83m      
陣列元素 500,000 1,000,000 2,000,000 5,000,000 10,000,000 20,000,000
建立陣列 0.63 1.324 2.462 6.104 12.37  
使用 + 0.344 0.409 1.057 1.717 3.527  
使用 string.concat() 0.172 0.325 0.701 1.847 4.012  
使用 array.join() 0.138 0.286 0.52 2.235 3.898  
             
瀏覽器 FireFox 版本: 15.0        
陣列元素 500,000 1,000,000 2,000,000 5,000,000 10,000,000 20,000,000
建立陣列 0.075 0.143 0.454 1.14 2.26 4.603
使用 + 0.039 0.048 0.078 0.407 0.417 0.944
使用 string.concat() 0.031 0.159 0.128 0.36 0.621 0.999
使用 array.join() 0.036 0.071 0.191 0.727 2.034  
             
瀏覽器 IE9(Win7)        
陣列元素 500,000 1,000,000 2,000,000 5,000,000 10,000,000 20,000,000
建立陣列 0.489 0.74 2.248 5.983 20.084  
使用 + 0.117 0.606 1.475 4.372    
使用 string.concat() 0.189 0.423 1.649 5.686    
使用 array.join() 0.107 0.203 0.377 2.65    
             
瀏覽器 IE10RC(Win8RC)        
陣列元素 500,000 1,000,000 2,000,000 5,000,000 10,000,000 20,000,000
建立陣列 0.332 0.518 1.146 3.435 10.025  
使用 + 0.052 0.306 0.247 1.265    
使用 string.concat() 0.064 0.106 0.338 1.042    
使用 array.join() 0.061 0.131 0.252 0.715    

 

沒有留言:

張貼留言