Yammer從Scala轉向Java
英文原文:Yammer Moving from Scala to Java
近日,由 Yammer 雇員 Coda Hale 發給 Typesafe 的 Scala 商業管理層的郵件通過 YCombinator 被泄漏出來并在 GitHub 上刊出。該郵件確認 Yammer 正在將其基礎設施棧從 Scala 遷回至 Java,原因在于 Scala 的復雜性與性能問題。
Yammer 的公關 Shelley Risk 向 InfoQ 證實該郵件只代表 Coda Hale 的個人意見而非 Yammer 的官方聲明;隨后,Coda Hale 又在 http://codahale.com/the-rest-of-the-story/上發表了一篇文章。在該文章中,Coda 澄清說這個消息是來自于 Donald Fischer(Typesafe 的 CEO)對早前一個 tweet 的回復。
更新:近日,Yammer 已經發布了聲明,宣布對該問題的立場,聲明證實了上述猜測。聲明還指出任何語言都會有瑕疵(不僅僅是 Scala),該郵件只不過是嘗試提出一些建議以改進 Scala 的性能與其他問題。最后,聲明說到在構建任何高性能項目時(Scala 是其產品環境)都有一些問題需要解決;該郵件旨在幫助 Scala 不斷改進。
雖然 Coda 并未打算公開該郵件,但他通過 Gist(后來被刪除了)將其放到了 GitHub 上以獲得其他朋友的反饋;然而,郵件內容后來被共享出來并得到了大范圍傳播。
回到 2010 年 8 月,Coda 在 Yammer Engineering 博客上說他們將要轉向 Scala。其目標是繼續運行在 JVM(出于性能原因)上,這個轉變的結果就是減少了約 50% 的代碼:
Artie 最初的原型采用 Java 編寫,但在一個周末的試驗中,我嘗試使用 Scala 2.8 重新實現一次。一天后,代碼行數減少了約一半,并添加了幾個特性。我震驚了,Java 開發者很容易找,但 Scala 團隊卻能完成更多工作
一年過后,這個決定發生了變化:
目前在 Yammer,我們正在將基礎設施遷回至 Java,同時以遺留庫的形式繼續對 Scala 提供支持。這個過程并不是那么急,我們剛剛開始,但需要很長時間。本質在于使用 Scala 而非 Java 作為我們的默認語言所產生的摩擦和復雜性并未被足夠的生產力提升或是維護工作的減少而抵消。我們或許還會在產品中使用 Scala,但主要的開發將會使用 Java。
Stephen Colebourne(近日發表了文章 Is Scala the new EJB2?)對這封郵件做了點評,其要點總結如下:
- 作為一門語言,Scala 中有很多頗具見地的想法。但它是門非常復雜的語言。
- 除了 Scala 所引入的概念與具體實現外,要想編寫地道的 Scala 還有一個文化的問題,有時突然就蹦出來一個最佳實踐:完全不管不顧社區。
- 我當然知道學習(以及教授)Scala 的困難程度與重要性。因為我們不可能在沒人學習 Scala 的情況下找到人,這個事實非常重要。
- 構建工具鏈導致開發很不舒服。這主要是因為 SBT 導致了 Maven 與 Ant 的邊緣化——而他們是 Java 生態圈中的兩個主要的構建工具。
- 每個主要的 Scala 發布都不兼容于之前的版本,這導致 Scala 開發者總是在開發新的庫并重新發明輪子。
- 借助于分析與檢查字節碼,我們可以通過采用一些簡單的規則實現 100 倍的改進:
- 不要使用 for 循環
- 不要使用 scala.collection.mutable
- 不要使用 scala.collection.immutable
- 總是使用 private[this]
- 不要使用閉包
- 我和開發團隊討論了這個問題(遷回至 Java),并且演示了兩個代碼基,結果是大家普遍同意進行切換。毫無疑問,我們肯定對 Scala 的某些方面還不太熟悉,但這不足以讓我們還固守在 Scala 上。
其中一些問題可能不太重要(比如說,一門語言越流行,那么雇傭的開發者的經驗就會越多),其中一些是根據經驗來測試的。比如說,其中一條建議就是不要使用 for 循環。這可以通過如下代碼進行測試:
scala>
var start = System.currentTimeMillis ();
var total = 0;for (i <- 0 until 100000) { total += i };
var end = System.currentTimeMillis ();
println (end-start);
println (total);
114
scala>
scala<
var start = System.currentTimeMillis ();
var total = 0;var i=0;while (i < 100000) { i=i+1;total += i };
var end = System.currentTimeMillis ();
println (end-start);
println (total);
8
這里使用 for 循環(與"until"模式,很多 Scala 程序員都習慣這么用)要比對應的 while 循環慢很多,雖然使用 while 循環的可讀性差一些。同樣循環的 Java 實現對于 for 和 while 來說都是 2ms。
我們做的另一個測試是通過從一個包含 Integer 對象的數據集合中加載來看看可變 map 的性能(這可以在 Java 與 Scala 中進行對比,裝箱的損耗應該差不多)。
scala>
val m = new scala.collection.mutable.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis ();
while (i<100000) { i=i+1;m.put (i,i);};
var end = System.currentTimeMillis ();
println (end-start);
println (m.size)
101
scala>
val m = new java.util.HashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis ();
while (i<100000) { i=i+1;m.put (i,i);};
var end = System.currentTimeMillis ();
println (end-start);
println (m.size)
28
scala>
val m = new java.util.concurrent.ConcurrentHashMap[Int,Int];
var i = 0;
var start = System.currentTimeMillis ();
while (i<100000) { i=i+1;m.put (i,i);};
var end = System.currentTimeMillis ();
println (end-start);
println (m.size)
55
與 java.util.HashMap 相比,性能是相同的,與 java.util.concurrent.ConcurrentHashMap 相比,Java 的速度要比 Scala 快一倍。Java 集合類超越了 Scala(以上測試基于 OSX JVM 1.6.0_29與 Scala 2.9.1,在文本撰寫之際的最新版本)。
但遺憾的是,在 Scala 庫 API 中有很多 Scala 集合,他們需要通過代碼中的隱式轉換從 Java 對象類型轉換為 Scala 對象類型。出于性能原因,這需要大量的重寫。
如果 Scala 編譯器通過 invokedynamic 生成代碼,那么閉包(lambdas)的性能還會得到改進,這是后續版本的 Scala 將會做的事情。此外,在 JDK 8 中(將會給 Java 帶來 native lambdas 與 method handles)將會有很多的性能改進,這些改進都可以為 Scala 所用。
最后,Scala 在解決版本之間的不兼容問題上面臨著越來越多的壓力(不僅僅是2.9.2與2.9.3之間的小改進)。Typesafe 并未發布 Scala 未來路線圖的官方聲明,也沒有說明何時才會有穩定的二進制版本能夠實現不同版本之間代碼的兼容。如果能夠實現向后兼容,那么就會有更多穩定的庫出現,并且會形成一個社區倉庫,這對未來有志于使用 Scala 的開發者將大有裨益。