為什麼Julia這麼快?

RogerL發表於2018-08-16

我本來說今年不會寫文章了,不過這個編輯器比知乎的好用多了啊!公式也很棒!以後不想再寫知乎了,我來這裡寫量子計算有人看麼?!真香!給你們寫編輯器的程式設計師加個雞腿好不好!好了回到正題

這是很多人都會問的一個問題。

我自己一直覺得自己講不清楚。都是codegen,憑啥Julia快,Julia能用LLVM,Python還有別的各種也能用啊?Julia到底有什麼好的?最近因為1.0了於是和一些人討論了下(比如紅紅),然後也Google了一下,這篇文章裡的內容大多整理自這些我聽到和看到的觀點。

然後我也被糾正了一個觀點:Julia只適合科學計算。聽了這些觀點以後我想Julia如果生態能夠做好,在這個階段能夠吸引到有技術能力的開發者嘗試或許可以出現很多不錯的東西。

從 Julia 的第一篇論文說起

讓我們回到Julia的第一篇論文裡去(我只是大概翻譯了部分,感興趣還請去看原文):

arxiv.org/pdf/1209.51…

在文章的開頭部分,可以看到實際上在一開始 Jeff Bezanson,Stefan Karpinski,Viral B. Shah,Alan Edelman 所嘗試要解決的是一般的兩語言問題,兩語言問題往往表現為對易用性(Convenience)和效能(performance)的妥協,程式設計師在需要描述演算法的高階(high level)而複雜的邏輯時使用容易使用的動態語言,而在效能敏感的地方往往會使用C,Fortran。這樣的方式對於一些應用很有效,但也是有缺點的。這在寫一些並行程式碼的時候,演算法複雜性會變得很大。而編寫向量化的(vectorized)程式碼,對於很多問題來說非常的不自然,並且可能會產生本可以由顯示的for迴圈所避免的中間變數。

由於需要去考慮兩種語言之間的型別轉換和記憶體管理,編寫兩種語言的程式碼可能會比用任何其中一種語言編寫的程式碼都要複雜。而如果處理不好兩層程式碼之間的介面,則有可能會大大增加優化的難度。

那麼另外一種解決方案就是增強我們已有的動態語言的效能,例如Python,比如像PyPy這樣的工程實際上已經非常成功了[1]。這些已有的工程都是嘗試去優化一個已有的語言,這是非常有用的,因為已有的程式碼可以直接獲益。但是這些方案都無法真正解決兩語言問題。以直譯器語言為假設的語言設計決定使得其能夠生成高效程式碼的能力收到了破壞。正如Henry Baker對Common Lisp的觀察:

...the polymorphic type complexity of the Common LISP library functions is mostly gratuitous, and both the efficiency of compiled code and the efficiency of the programmer could be increased by rationalizing this complexity. [2][3][4]

Julia在設計之初就考慮如何讓其利用現代的技術去高效加速動態語言。從結果上來看Julia在提供了像Python,Lisp,Ruby這樣互動式程式設計和動態性的同時,也有著靜態編譯語言一般地效能。

Julia的效能主要是由這樣三點特性所獲的:

  1. 通過多重派發(multiple dispatch)自然獲得地充分的型別資訊
  2. 對動態型別激進地(aggressive)程式碼特化(code specialization,比如C++的template就是一個例子,注)
  3. 利用LLVM的JIT編譯

實際上到這裡我們就已經看到Julia的速度不是簡單地靠產生LLVM,而是由語言本身的設計所帶來的

在過去嘗試優化動態語言的動作中,研究人員已經觀察到了實際上程式可能並不是程式設計師們所想象的那麼動態[5]

We found that dynamic features are pervasive throughout the benchmarks and the libraries they include, but that most uses of these features are highly constrained...

從這一點來說,現有的程式語言設計可能並未找到一個良好的平衡點。有很多程式碼實際上都可以是靜態型別的,並被更高效地執行。但是由於語言本身的設計並不能實現這一點。我們假設以下的 “動態性” 是更加有用的:

  1. 能夠在載入和編譯時期執行程式碼的能力,這可以使得編譯系統和配置檔案更容易
  2. 將一個一般的任意型別(Any type)作為唯一的真正的靜態型別,使得可以在需要的時候忽略靜態型別
  3. 不要拒絕形式上優美的程式碼
  4. 程式行為僅僅由執行時的型別決定(例如沒有靜態過載)

而Julia拒絕了一些阻礙優化的特性(例如CLOS [6]),而有如下的限制:

  1. 型別本身是不可變的
  2. 一個值的型別在其生存週期內是不可變的
  3. 區域性變數的環境不會被具體化(reified)
  4. 程式程式碼不可變(注,但是可以產生新的程式碼然後被執行,這大概體現在巨集的world裡)
  5. 不是所有的繫結都是可變的(允許定義常數)

這些限制使得編譯器可以看到所有具體的本地變數,然後僅僅根據區域性資訊就可以進行分析。

文章我就不全翻譯了,那麼Stefan在mail list裡大概總結了一下,Julia的效能主要是由以下幾點帶來的:

  1. an expressive parametric type system, allowing optional type annotation
  2. multiple dispatch using type annotations to select implementations
  3. dataflow type inference, allowing most expressions to be concretely typed
  4. careful design of the language and standard library to allow analysis
  5. aggressive code specialization on run-time types
  6. just-in-time compilation (using LLVM).

可以看到作為語言本身特性的引數型別系統和多重派發(這甚至直接影響了Julia程式碼編寫時的設計)是非常重要的。

同時Stefan也評論:

LLVM is great, but it's not magic. Using it does not automatically make a language implementation fast. What LLVM provides is freedom from doing your own native code generation, as well as a number standard low-level optimizations. You still have to generate good LLVM code. The LLVM virtual machine is a typed register machine with an infinite number of write-once registers – it's easier to work with than actual machine code, but not that far removed (which is the whole point).

實際上我們可以看到,說現在codegen已經爛大街的言論是非常淺薄的。而認為Julia毫無創新只是C++,R,Python的混合的言論也是(無法描述)的。

總結一下,Julia實際上是對原本的一些動態語言做了一些限制的結果,它在嘗試尋找一個更優的平衡點。說它繼承了Python的簡單是錯誤的,說它繼承了R也是錯誤的,Julia也更沒有繼承C++。Julia所想表達的是,我們也許可以犧牲一些不那麼重要的動態性,就能夠換來非常驚人的速度。

至於這樣的平衡是否就是最優的,那麼就仁者見仁智者見智了吧。

一些嘗試

那麼實際上,有一些嘗試挑戰C/Fortran的嘗試:

純Julia實現一個BLAS:

discourse.julialang.org/t/we-can-wr…

純Julia實現的一個HDF5:

github.com/simonster/J…

純Julia實現的一個JSON(根據紅紅評價,這個他可以做的更好):

github.com/quinnj/JSON…

從這一點看來,我的認識其實之前也是不正確的,除了更加統一的多維陣列(這對物理學家非常重要,不然也不會有那麼多人還用Fortran)以外,也許我們還可以有更加廣泛的應用,這不僅僅限於科學計算,機器學習,而是更多的過去需要兩語言問題來解決的地方。

但是相對的,過去用一種語言就可以解決的問題,或許這樣一個大殺器也用起來並不順手。我想這樣大概足夠客觀地描述Julia了,大家也可以從中去體會到它適合什麼場景不適合什麼場景。

最後,我個人覺得,目前Julia不適合小白。也不適合想要找工作的人。但是它更適合那些過去被兩語言問題所折磨的人。


[1]: C. F. Bolz, A. Cuni, M. Fijalkowski, and A. Rigo. Tracing the meta-level: Pypy’s tracing jit compiler. In Proceedings of the 4th workshop on the Implementation, Compilation, Optimization of Object-Oriented Languages and Programming Systems, ICOOOLPS ’09, pages 18–25, New York, NY, USA, 2009. ACM.

[2]: H. G. Baker. The nimble type inferencer for common lisp-84. Technical report, Tech. Rept., Nimble Comp, 1990.

[3]: R. A. Brooks and R. P. Gabriel. A critique of common lisp. In Proceedings of the 1984 ACM Symposium on LISP and functional programming, LFP ’84, pages 1–8, New York, NY, USA, 1984. ACM.

[4]: F. Morandat, B. Hill, L. Osvald, and J. Vitek. Evaluating the design of the R language. In J. Noble, editor, ECOOP 2012 Object-Oriented Programming, volume 7313 of Lecture Notes in Computer Science, pages 104–131. Springer Berlin / Heidelberg, 2012.

[5]: M. Furr, J.-h. D. An, and J. S. Foster. Profile-guided static typing for dynamic scripting languages. SIGPLAN Not., 44:283–300, Oct. 2009.

[6]: H. G. Baker. Clostrophobia: its etiology and treatment. SIGPLAN OOPS Mess., 2(4): 4–15, Oct. 1991.

相關文章