在我加入Eigenharp儀器後,今後告別了Java開發。新工做須要開發跨平臺的實時音頻軟件,要求處理大量數據輸出和低延遲。開發工做基本上使用C++完成,用到了Juce函數庫和用CPython編寫的一些膠水代碼(glue code)。我被安排開發音樂演奏軟件。在作技術選型時,我意識到了那些在Java世界裏習覺得常的事情(其實不那麼簡單)。html
但有哪些事情讓Java變得很棒?列出Java很棒的10個緣由並不困難。你能夠已經注意到了其中的一些,然而在開發選型時大聲說出Java的好處是頗有技巧的。下面是我鍾愛Java的幾個理由:java
咱們從Java編譯器開始,後面的幾點會在接下來的文章中詳細討論。python
Java編譯器的優異之處shell
Java編譯器多是做爲開發者遇到的第一個平臺組件。在開始學Java語言時,須要用它來編譯你的「Hello World」程序,經過它將你的源代碼轉爲可執行程序。macos
沒有字節碼(bytecode)就沒有Java編譯器。除了字節碼自己的優勢(後面的文章會有討論),這種中間形式還支持運行時JIT(即時編譯)。性能優化
JVM的即時編譯所向無敵微信
你可能認爲JIT只是一種性能改進,讓解析器的運行速度匹配本地程序。但實際上,JIT的速度比本地程序更快。若是像C++那樣直接編譯成本地程序,須要在編譯時進行靜態優化(static optimization)。這種作法過早地考慮了性能優化,與在發佈時帶有運行時監控功能的作法截然不同。提早優化改變了不少指令,這與你在源代碼中表達的內容造成了差距,程序的實際執行結果可能會與代碼中的邏輯有所差異。架構
遇到這種狀況,一般的作法是人爲調整優化級別,指望以此獲得能夠正確運行的程序。在深刻編譯器細節以前,一般沒法知道分析器(profiler)會對代碼的執行產生怎樣的影響,當前的優化級別是否正確。有時惟一可行的作法是經過變動集合(change set)及時回退到以前的工做,此時你的代碼與驗證過的問題之間已經再也不有任何聯繫。隨着項目日益龐大,一般優化級別會再也不那麼激進,這種狀況一般沒法找出更高的優化級別爲何會引入問題。模塊化
優化開關的「黑盒」特性使得這種狀況越發糟糕。下面是你在clang手冊裏能夠找到的全部內容,但願你能從中幸運地發現有用的信息:函數
代碼生成選項
-O0 -O1 -O2 -Os -Oz -O3 -Ofast -O4
請指定須要使用的優化級別
-O0 表示「沒有優化」:該級別編譯速度最快,而且生成的代碼可調式性最佳。
-O2是中間級別的優化,能夠生成最優化的代碼。
-Os與-O2相似,能夠額外減小代碼大小。
-Oz與-Os(以及-O2)相似,可是代碼大小會進一步減少。
-O3與-O2相似,可是編譯過程稍長生成的代碼大小也稍大(這樣可使得代碼執行速度更快)。
-Ofast具有全部-O3優化功能,可是因爲優化程度過大可能與一些語言標準不兼容。
在某些平臺下,-O4會開啓連接時優化(link-time optimization);對象文件會以LLVM bitcode文件格式存儲,全部優化在連接時執行。
-O1優化位於-O0和-O2之間。
專一你的代碼,而不是編譯器結構
因爲Java編譯器的惟一工做是將源代碼轉爲字節碼,使用javac命令變得很是簡單。一般你全部須要關心的就是,設置正確的classpath信息、選擇兼容的VM版本、確認class文件存儲的位置,全部的3個編譯選項就是: -classpath、-target和-d。
C++的選項更多也更復雜。下面展現了相對簡單的一個g++編譯器調用。裏面包括了一些項目相關的flag以及頭文件-I flag,相似javac的classpath。這些都是C++的標準習慣,一般也是進行模塊化和提供平臺獨立構建的惟一辦法。
1
2
3
4
5
6
7
8
9
|
g++-4.2 -o tmp
/obj/eigend-gpl/piagent/src/pia_buffer
.os -c -arch i386
-DDEBUG_DATA_ATOMICITY_DISABLED
-DPI_PREFIX=\"
/usr/pi/Python
.framework
/Versions/2
.5\"
-mmacosx-version-min=10.6 -ggdb -Werror -Wall -Wno-deprecated-declarations
-Wno-
format
-O4 -fmessage-length=0 -falign-loops=16 -msse3 -DALIGN_16
-DBUILDING_PIA -fvisibility=hidden -fPIC -Isteinberg -Ieigend-gpl
/steinberg
-Ieigend-gpl -I. -I
/usr/pi/Python
.framework
/Versions/2
.5
/include/python2
.5
-Itmp
/exp
eigend-gpl
/piagent/src/pia_buffer
.cpp
|
最大的問題在於每一個編譯器都有不一樣的選項。G++與Clang不一樣,與C++編譯器也不一樣,此外還有Visual Studio C++編譯器等等。對經常使用的命令行參數,這些編譯器使用不一樣的命名,各自支持不一樣版本的C++標準或標準的不一樣子集。此外,它們還提供特定編譯選項。
若是指望得到最佳性能,你須要從每一個編譯器的數百個編譯選項中艱難地搜尋,有時還須要具有目標硬件平臺的底層知識。更糟糕的是,你須要提早作好準備指望編譯選項能夠正常運行在全部平臺或處理器架構上,不然當程序運行失敗後沒有任何辦法進行追蹤。每次發佈我都提心吊膽,由於找出客戶報告中軟件崩潰的緣由是個人主要職責。有幾回,咱們不得不在同一個處理器上不斷調整編譯器選項,重現問題並生成穩定的二進制。
沒有靜態連接,也沒有動態連接,只有運行時連接
當你生成本地二進制時,你能夠選擇如何連接本身的二進制或者模塊。靜態連接將全部內容打包到一個可執行文件裏,這樣沒法獨立更新函數庫,也不能生成更大的二進制文件。運行時連接更容易發佈,並且沒有動態連接的上述問題。在實際開發中,一個大型產品單獨發佈靜態連接生成的可執行程序是不現實的。即便你將構建拆分紅多個模塊或者調用第三方函數庫,早晚仍是須要面對動態連接問題。
動態連接很是複雜並且會引入不少麻煩。除了代碼內部的可見性(private、public等),你須要單獨編譯本身的API,確保建立的動態連接庫導出正確的符號(symbol)。另外一方面,你須要爲實際使用函數庫的代碼導入這些符號。若是使用了同一個函數庫的頭文件和客戶端,須要在所處的編譯環境下爲你的API聲明須要傳入的正確參數。除此以外,在MacOSX、Linux、Windows上動態連接也有着不一樣的含義。你須要整理代碼,爲不一樣的平臺使用不一樣的宏定義(macro)。固然,可能還須要維護一個通用的代碼庫(codebase)以支持全部平臺。立刻你就會發現,實際的編譯過程當中編譯器會將連接應用到代碼的每一個聲明,有時你最終使用的class結構會由編譯器規定。
即便一切就緒,還須要編譯不少共享函數庫——尤爲是在Windows平臺上,每一個用戶都須要面對DLL版本不兼容問題。儘管在新版本的Windows上這個問題有所改善,但你的二進制仍是綁定到了特定版本的DLL,一個看似很小的API變化都會讓動態連接再也不兼容。要解決這個問題,能夠將這些DLL做爲應用程序的私有綁定,或者在運行時動態加載。若是使用動態加載,須要格外注意在應用程序內部進行鏈接和符號解析。而實際上你不該該關心這些細節。
Java經過運行時連接繞過了全部這些問題。全部內容都是動態的,經過字節碼導出的符號與包沒有直接關聯,基本上不須要關心連接過程。若是確實有須要,你還能夠動態地加載類或方法,可是這種狀況不多出現。字節碼的符號表示是很是穩定的,即便class的版本不斷演變以前編寫的代碼仍是能夠直接運行,除非函數庫的做者故意修改API。
接下來
這是Java 10大有點第一部分。請留言或者經過tweet @gbevin聯繫我。下一篇會討論Core API,相信會給你留下更深的映像。不要換臺哦!
-- 掃描加關注,微信號: importnew --
原文連接: zeroturnaround 翻譯: ImportNew.com - 唐尤華
譯文連接: http://www.importnew.com/6268.html
[ 轉載請保留原文出處、譯者、譯文連接和上面的微信二維碼圖片。]