只針對「本機二進位」(直接使用客戶機器 CPU 的指令)。任何中間指令如 LLVM IR 都不算在內。


是的,所以linux的軟體都是用源代碼分發(蜜汁微笑)


不是。

首先,任何非二進位的分發的程序都需要依賴一種嚴格意義上的解釋執行,比如 python bash perl 腳本文件頭上的那串 #! 指向的解釋器的路徑,或者是二進位程序中的動態鏈接也需要解釋器去進行。

早期的操作系統概念中規定,除了內核之外,一個系統還需要有配套的Shell,編輯器和編譯器。最普遍就是 GNU 全家桶。那麼這種情況下當你需要獲得一個軟體的分發的時候,考慮到C源碼分發在那個時代也是一個普遍的行為(畢竟C就是為了這種需求產生的),在你當前的平台上進行編譯得到對應的二進位。

但是隨著軟體的複雜化,動態鏈接,包管理等等技術被提出,他們被用來改善不同的問題。並且對於很多無需開發的場景,比如說一般的消費者使用的機器,就沒有必要部署以編譯器為核心的工具鏈。另外各種靜態庫依賴和運行時的動態庫依賴的出現,意味著在沒有網路的情況下純粹分發源碼,這會是個很麻煩的事情,特別是有些商業軟體出於保護自身知識產權的情況下。

另外一個軟體項目也有多個平台的需求,那麼這就意味著通常一個代碼庫裡面會有多份平台的專用代碼,使用全局的構建系統進行控制,或者在一個代碼文件中用局部宏控制。如此一來代碼的體積就會變得很龐大,編譯通常會佔用不少的時間,也不利於軟體的分發。

不過從今天的角度看,二進位分發最大的幾個壞處還是在於

  1. 安全性。純粹的二進位代碼分析難度較源碼來說更難,開源軟體通常是可被檢驗的。
  2. 一些場景下的性能。雖然可能分發商使用了更好的編譯器(比如 intel 的 icc 用於構建)會帶來更好的性能,但是過了些年頭,比如 x86 開始過渡到 x86_64,ARMv7 轉向 ARMv8,這種變化如果用戶沒有源碼,那麼意味著新架構的分發和修正都無法進行。
  3. 兼容性,很顯然會受限在目標硬體平台和有關 ABI。當然現在也有一份二進位文件裝載多個平台的實現。

所以基於如下壞處,Apple 要求 Watch 開發者提交 LLVM bitcode,這就讓 Apple Watch S4 從 ARMv7k 到 ARMv8 的 32bit ABI 下實現了順利過渡。而在 Android 上,Java 的位元組碼完全無視架構,通過 AOT 運行,可以選擇當前平台的優化;二進位部分可以通過上游分發渠道,類似 Linux 包管理那樣區分特定平台,全平台的軟體包。iPhone 通過對於開發生態的嚴格控制,在新的系統的手機上完全移除了 32bit ABI 和有關支持。


計算機相關標準的設計啊,當然要盡量優雅高效,但是也要考慮到歷史的行程。

「軟體使用本機二進位代碼分發」的對立面是什麼?是「使用多平台統一的二進位代碼」對吧?那麼既然多平台統一的代碼不一定能夠直接跑在端側CPU上,就只能通過JIT、JVM一類的技術,花費額外的運行時間來解析/本地適配這些代碼才能讓它們運行起來對吧?在標準剛剛訂立的年代,這樣高額的運算開銷是不可能被接受的。

我們以主流的多平台可執行文件格式的誕生時間作為參考,Windows家族第一次引入PE文件格式是在1993年的Windows NT 3.1,Unix系第一次引入ELF文件格式是在1988年的SVR4。

而那個時代的硬體是個什麼德行呢?1990年最頂尖的CPU,是Intel發售的一款486處理器,達到了足足33MHz的運算速度。現在的家用筆記本隨隨便便就是雙核、四核+GHz級別的運算量,在那個年代這種運算速度就跟做夢一樣。

但是歷史其實也沒有等太久,1996年就出現了第一款500MHz的CPU(DEC的Alpha處理器),雖然是曇花一現,但是同期其他CPU的運算速度也已經達到200HMz上下了,土壤已經成熟了。也就是在那一年,Sun公司開始試水Java,嘗試在「多平台使用統一的二進位代碼」。但是那個時候PE格式和ELF格式相關的標準都已經廣泛落地了,Sun公司不可能左右整個業界修改一項已經成熟的標準,於是整個行業就發展成了現在的樣子。


當年不開源、也沒有JIT技術、CPU慢得要死的時候沒得選吧


性能追求是永恆的,尤其是早期性能弱的情況下更是不能放過任何優化機會,所以當時來說是必然的選擇

現在套一層 vm 效率可以接受的地方,用用也無妨,比如 java/electron 現在的地位。但這畢竟依賴/受制於 vm,且缺乏普適性。

你說 現在 大部分 效率不要緊 的應用搬上 vm 那當然是可以的,但 所有 肯定不行。


推薦閱讀:
相关文章