[翻譯] 拆穿 Java StringBuilder 的謠言

看板java作者 (痞子軍團團長)時間11年前 (2013/04/01 20:12), 編輯推噓1(109)
留言10則, 4人參與, 最新討論串1/8 (看更多)
※ [本文轉錄自 Translate-CS 看板 #1HMNc6jg ] 作者: PsMonkey (痞子軍團團長) 看板: Translate-CS 標題: [翻譯] 拆穿 Java StringBuilder 的謠言 時間: Mon Apr 1 20:11:45 2013 原文網址:http://skuro.tk/2013/03/11/java-stringbuilder-myth-now-with-content/ 譯文網址:http://blog.dontcareabout.us/2013/04/java-stringbuilder.html BBS 版以 markdown 語法撰寫 喔對,這篇真的不是愚人節活動 ______________________________________________________________________ 謠言...... ========== > 用 + 號來連接兩個字串是萬惡的根源。 —— 不知名的 Java 開發人員 **註:**這裡討論用到的程式碼都可以在 [Github] 上找到。 在大學的時候,我學到在 Java 中用 + 號來連接字串是一種致命的效能罪惡。 最近在 [Backbase R&D] 有一個內部的 review, 這個 recurring mantra 變成了謠言, 因為當你使用 + 號來連接字串時,`javac` 會在底層使用 `StringBuilder`。 我要證明這件事情,並驗證在不同環境下的真實性。 [Github]: https://github.com/skuro/stringbuilder [Backbase R&D]: http://www.backbase.com/ 測試...... ========== 倚賴 compiler 對連接字串這件事作最佳化, 這意味著使用不同的 JDK 可能會得到完全不一樣的結果。 就我平常的工作環境,我考慮這三個 JDK 供應商: * Oracle JDK * IBM JDK * ECJ(僅針對開發人員) 此外,雖然我們官方支援 Java 5 跟 Java 6, 不過我們也在研究讓產品可以支援到 Java 7, adding another three-folded level of indirection on top of the three vendors.(譯註:翻譯不能 Orz) 為了<strike>懶惰</strike>簡單起見, `ecj` compile 出來的 bytecode 只會在 Oracle JDK 7 上頭執行。 我準備了一個 [VirtualBox] VM 安裝上述所有的 JDK, 然後我寫了一些 class 來代表三種不同的字串連接方式, 每一個 method 會有三到四個連接字串的動作、取決於 test case。 [VirtualBox]: https://www.virtualbox.org/ 這些 test case 在每一回合會執行一千次、總共 100 回合。 同一個 test 的所有回合都會在同一個 VM 上頭執行, 在跑不同 test case 的時候重開 VM, 這都是為了讓 Java 在執行期可以作任何可能的最佳化動作、 而不會影響到其他 test case。 所有 JVM 啟動時都用預設的設定。 更細節的部份可以參考 benchmark runner [script]。 [script]: https://github.com/skuro/stringbuilder/blob/master/bench.sh 程式碼 ====== 所有 test case 以及 test suite 的完整程式碼都在 [Github] 上頭。 下面這幾個不同的 test case 用來測量 + 號與直接使用 `StringBuilder` 連接字串的效能差異: // String concat with plus String result = "const1" + base; result = result + "const2"; ______________________________________________________________________ // String concat with a StringBuilder new StringBuilder() .append("const1") .append(base) .append("const2") .append(append) .toString(); } ______________________________________________________________________ // String concat with a StringBuilder new StringBuilder("const1") .append(base) .append("const2") .append(append) .toString(); } 大體上的想法是在常數字串前後都連接一個變數。 最後兩個 case 都是用 `StringBuilder`, 差異是後頭使用了傳入一個參數的 constructor, 在 builder 初始化的時候就初始化結果的一部分。 結果...... ========= 前提講的差不多了,下面這些產生出來的圖表, 每一個點對應到單一個測試回合(對同一個測試執行 1000 次)。 後頭會討這些結果以及一些有趣的細節。 ![plus](http://skuro.tk/img/post/catplus.png) ![StringBuffer()](http://skuro.tk/img/post/catsb.png) ![StringBuffer(String)](http://skuro.tk/img/post/catsb2.png) 討論...... ========== Oracle JDK 5 輸的很徹底,跟其他相比就是個 B 咖。 但那不是這次要討論的範圍,所以暫時不理會吧。 在上頭的圖表當中我觀察到兩件有趣的事情。 首先,使用 + 號跟明確指定用 `StringBuilder` 的確存在普遍的落差, *特別是*在使用 Oracle Java 5 的時候比其他人差了三倍。 第二個觀察到的現象是,大多數的 JDK 在明確指定用 `StringBuilder 時可以提供比 + 號快兩倍的速度, 而 **IBM JDK 6 看起來沒有減損任何效能**, 在所有的 test case 中始終保持在 25ms 左右的時間。 仔細看一下產生出來的 bytecode 揭露了一些有趣的細節。 bytecode 表示: ============== **註:**[Github] 上也有 decompile 後的 class。 在所有的 JDK 上應該**總是**用 `StringBuilder` 來實作連接字串, 即使有 + 可以用。 此外,比較所有供應商的所有版本, 在同樣的 test case 下**幾乎沒有什麼分別**。 唯一比較有區隔的是 [ecj],它是唯一一個對 `CatPlus` test case 作最佳化, 會使用傳入一個參數的 `StringBuilder` constructor,而不是 `StringBuilder()`。 [ecj]: https://github.com/skuro/stringbuilder/blob/master/ecj/CatPlus.class.txt 比較產生的 bytecode 可以看到在不同情境下可能會影響效能的部份: * 用 + 號連接字串時,每一次都會建立一個**新的** `StringBuilder` **instance**。 這很容易導致效能下降,因為要產生一堆用完就丟 instance, 而造成 garbage collector 的壓力。 * compiler 會依照字面上的意思, 只有在你指定用傳入一個參數的 `StringBuilder` constructor, compiler 才會用它。 這分別導致 [CatSB] 呼叫了四次 `StringBuilder.append()`、 而 [CatSB2] 呼叫三次。 [CatSB]: https://github.com/skuro/stringbuilder/ blob/master/ecj/CatSB.class.txt [CatSb2]: https://github.com/skuro/stringbuilder/ blob/master/ecj/CatSB2.class.txt 結論...... ========= 分析 bytecode 提供了問題的最終答案: > 需要明確指定用 `StringBuilder` 來增進效能嗎? > **是的!** 上面的圖表顯示的很清楚了,用 + 號會損失 50% 的效能; 除非你用 IBM JDK 6, 那只會筆明確指定使用 `StringBuilder` 稍微差一點點。 此外,看 *JIT 最佳化* 如何影響整體效能十分有趣。 例如:即使兩個指定使用 `StringBuilder` 的 test case, 它們的 bytecode 看起來不一樣, 但是長時間運作之後它們得到的結果還是幾乎一樣的。 ![confirmed](http://skuro.tk/img/post/myth-confirmed.jpg) -- 錢鍾書: 說出來的話 http://www.psmonkey.org 比不上不說出來的話 Java 版 cookcomic 版 只影射著說不出來的話 and more...... -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 114.25.15.134 ※ 發信站: 批踢踢實業坊(ptt.cc) ※ 轉錄者: PsMonkey (114.25.15.134), 時間: 04/01/2013 20:12:01

04/01 21:03, , 1F
題外話 連結都牽連後面的 ) 造成無法連接 感謝分享
04/01 21:03, 1F
這... markdwon 語法就是這樣耶... 真抱歉... 只能說,歡迎看 blog 版 update:修正一些 typo ※ 編輯: PsMonkey 來自: 114.25.15.134 (04/01 22:13)

04/01 22:53, , 2F
剛測了三個自行改的+的用法,和原文網址的回應有所呼應,
04/01 22:53, 2F

04/01 22:55, , 3F
若是全部+都在同一敘述,速度會和SB一樣. 這樣看來, 原plus
04/01 22:55, 3F

04/01 22:56, , 4F
blog的回應是說 compile 之後再 de-compiler 會變成
04/01 22:56, 4F

04/01 22:57, , 5F
碼中, result做了兩次assignment, 因此速度變慢了
04/01 22:57, 5F

04/01 22:58, , 6F
StringBuilder 嗎??
04/01 22:58, 6F

04/01 22:59, , 7F
我沒看bytecode. 只是猜compiler沒做好佳化,每次的assignme
04/01 22:59, 7F

04/01 23:01, , 8F
nt, 可能都轉譯去new 一個SB出來
04/01 23:01, 8F

04/01 23:08, , 9F
我補了,在下一篇文章當中 Orz
04/01 23:08, 9F

04/02 13:37, , 10F
有哦,看了blog板 感恩 :)
04/02 13:37, 10F
文章代碼(AID): #1HMNcJzU (java)
討論串 (同標題文章)
文章代碼(AID): #1HMNcJzU (java)