當前位置:
首頁 > 科技 > 一文帶你感受GraalVM的十大用途

一文帶你感受GraalVM的十大用途

作者| Chris Seaton

譯者| 無明

編輯| 張嬋

不久前 Oracle 發布了 GraalVM,一套通用型虛擬機,能執行各類高性能與互操作性任務,並在無需額外成本的前提下允許用戶構建多語言應用程序。

GraalVM 包含了很多不同的部分,我們將列出 GraalVM 的一些不同的特性,並展示它的用途。

高性能 Java

佔用內存小、啟動速度快的 Java

組合 JavaScript、Java、Ruby 和 R 語言

在 JVM 上運行本地語言

適用於所有編程語言的工具

擴展基於 JVM 的應用程序

擴展本地應用程序

將 Java 代碼作為本地庫

資料庫中的 polyglot

創建自己的語言

由於篇幅過長,我們只在本文中詳細分享前 6 大用途。關於 GraalVM 的更多精彩內容,也可訂閱或試讀 Oracle Labs 的高級研究員鄭雨迪的專欄《深入拆解 Java 虛擬機》。鄭雨迪會在單獨拿出一個模塊的內容來分享Oracle 的虛擬機黑科技,科普 GraalVM 的各個組成部分,其中包括編譯器 Graal,語言實現框架 Truffle,以及支持 Ahead-of-Time(ATO)編譯的 SubstrateVM。

我們可以使用 GraalVM 1.0.0 RC 1(https://www.graalvm.org/downloads)來重現本文所述的內容。我是在 macOS 上運行 GraalVM 企業版,不過在 Linux 上運行 GraalVM 社區版也是一樣的。文中運行的代碼可以從 http://github.com/chrisseaton/graalvm-ten-things 下載。

安 裝

我從 https://www.graalvm.org/downloads 下載了 GraalVM 1.0.0 RC 1 企業版,並將它放到 $PATH 路徑中。

GraalVM 內置了 JavaScript,並帶有一個叫作的軟體包管理器,可用它來安裝其他語言。我已經安裝了從 GitHub 下載的 Ruby、Python 和 R 語言。

我們可以通過運行 java 或 js 來獲得這些運行時的版本信息。

高性能

GraalVM 中的 Graal 得名於 Graal 編譯器。Graal 是一種「萬能」編譯器,也就是說,雖然它是單一的實現,卻可以用於很多用途。例如,我們可以使用 Graal 進行預編譯(ahead-of-time)和即時編譯(just-in-time),也可用於編譯多種編程語言。

我們可以將 Graal 簡單地用作 Java JIT 編譯器。

以下的示常式序將會輸出一篇文檔的前十個單詞,它使用了 Stream 和 Collector 等 Java 語言特性。

GraalVM 包含了一個 javac 編譯器,但在本例中,它與標準的編譯器並沒有什麼區別。因此,如果你願意,也可以使用系統的 javac。

如果我們運行 GraalVM 提供的 java 命令,將會自動調用 Graal JIT 編譯器,不需要做額外的配置。我使用 time 命令來獲得整個程序從開始到運行結束所花費的時間,而不是進行複雜的微基準測試。我使用了大量的輸入,這樣就不用去糾結幾秒鐘的差別。我使用的 large.txt 文件大小為 150MB。

Graal 是使用 Java 開發的,而其他大多數 Java JIT 編譯器是使用 C++ 開發的。我們因此能夠比其他編譯器更快地改進它,而且它具備了強大的優化功能,比如 HotSpot 標準 JIT 編譯器中所沒有的部分轉義分析功能。這項優化功能可以讓 Java 程序的運行速度明顯加快。

為了與不使用 Graal JIT 編譯器時的速度進行比較,我使用 -XX:-UseJVMCICompiler 標記來運行程序。JVMCI 是 Graal 和 JVM 之間的介面。當然,我們也可以拿它與標準的 JVM 進行比較。

結果顯示,使用 Graal 運行程序的時間大約是標準 HotSpot 的四分之三。在習慣於將單個位數的百分比性能增長視為顯著改進的今天,這個數字算得上是一個巨大的提升。

Twitter 是在生產環境中使用 Graal 的公司之一,他們表示,Graal 確確實實為他們省下了不少錢。Twitter 使用 Graal 來運行 Scala 應用程序。因為 Graal 執行的是 JVM 位元組碼,因此適用於任何基於 JVM 的語言。

這是 GraalVM 的第一個用途——將它作為 Java 應用程序的一個更好的 JIT 編譯器。

佔用內存小、啟動速度快的 Java

Java 對於長時間運行的進程來說是相當強大的,但短時間運行的進程可能會因較長的啟動時間和較高的內存佔用而飽受其苦。

例如,如果我們使用更小的輸入來運行相同的應用程序(文件大小約為 1KB,而不是 150MB),似乎需要花費更長的時間,並且需要 60MB 的內存。我們使用 -l 選項列印出它所消耗的內存和運行時間。

GraalVM 為我們提供了解決這個問題的工具。可以說 Graal 就是一個編譯器軟體包,有許多不同的使用方式,其中之一就是將源碼預編譯為本地可執行鏡像,而不是在運行時進行即時編譯。這與傳統的編譯器 gcc 類似。

這個命令會生成一個名為 topten 的本地可執行文件。這個可執行文件並不是 JVM 的啟動程序,它不需要鏈接到 JVM,也不以任何方式捆綁 JVM。native-image 會將 Java 代碼和所使用的 Java 庫直接編譯成機器碼。我們還使用了 SubstrateVM 虛擬機,它和 Graal 一樣,也是用 Java 編寫的。

如果我們看一下 topten 所使用的庫,就會發現它們都只是標準的系統庫。我們也可以將這個文件移動到一個從未安裝過 JVM 的系統上運行,以此來驗證它確實沒有使用 JVM 或任何其他文件。它的體積很小——這個可執行文件不到 6MB。

與在 JVM 上運行相同的程序相比,可執行文件的啟動速度快了一個數量級,使用的內存少了一個數量級。它的速度非常快,快到讓你注意不到在命令行上所花費的時間,而且感覺不到停頓。

不過,native-image 這個工具也存在一些約束,比如所有的類在編譯期間都必須可用。除此之外,在反射方面也存在一些限制。除了基本的編譯功能之外,它還提供了額外的高級特性,即在編譯期間運行靜態初始化器,以便在載入應用程序時可以少做一些事情。

這是 GraalVM 的第二個用途——以低內存佔用和快速啟動來運行現有的 Java 程序。它為我們省去了一些配置問題,比如如何在運行時查找正確的 jar 文件。它還能讓 Docker 鏡像的體積變得更小。

組合 JavaScript、Java、Ruby 和 R 語言

除了 Java,GraalVM 還包含了 JavaScript、Ruby、R 語言和 Python 的實現。它們都是使用一個叫作 Truffle 的語言實現框架開發的,Truffle 讓實現簡單且高性能的語言解釋器成為可能。在使用 Truffle 開發語言解釋器時,會自動使用 Graal 作為 JIT 編譯器。因此,Graal 不僅是 Java 的 JIT 編譯器和預編譯器,也可以是 JavaScript、Ruby、R 語言和 Python 的 JIT 編譯器。

GraalVM 中的語言旨在成為現有語言的直接替代品。例如,我們可以安裝一個 Node.js 模塊:

我們可以使用此模塊編寫一個小程序,將 RGB HTML 顏色轉換為 HSL:

然後用常規的方式運行它:

GraalVM 提供了一個 API,用以在一門語言中運行另一門語言的代碼。因此,我們可以使用多種語言來開發一個應用程序。

我們可能希望用一種語言開發應用程序的主要部分,同時又使用另一種語言的軟體包。例如,假設我們用 Node.js 開發一個將 CSS 顏色名稱轉換為十六進位數的應用程序,但又希望使用 Ruby 顏色庫來完成轉換。

我們以字元串形式提供了一小串 Ruby 代碼——導入必要的庫,然後返回一個 Ruby 對象。要在 Ruby 中使用這個對象,通常需要這樣:Color::RGB.by_name(name).html。而在我們的例子中,我們是在 JavaScript 里調用這些方法,即使它們是 Ruby 的對象和方法。並且,我們傳給它一個 JavaScript 字元串,然後把結果連接起來,結果里包含了 Ruby 字元串和其他 JavaScript 字元串。

下面安裝 Ruby 和 JavaScript 的依賴項。

然後,我們需要使用以下幾個選項來運行 node:--polyglot 表示我們想要訪問其他語言,--jvm 表示要使用 JVM,因為默認情況下 node 本地鏡像只包含了 JavaScript。

然後在瀏覽器中打開 http://localhost:8080/css/aquamarine。除了 aquamarine,也可以使用其他顏色名稱。

圖片: https://images-cdn.shimo.im/maemEQ9DMI0JZAtY/1.png!thumbnail 接下來讓我們嘗試使用更多的語言和模塊。

對於任意大的整數,JavaScript 並沒有很好的解決方案。我發現了幾個像 big-integer 這樣的模塊,但這些模塊的性能並不好,因為它們將數字的組成部分存儲為 JavaScript 浮點數。Java 的 BigInteger 性能更好,所以我們用它來做一些任意大的整數運算。

JavaScript 也不提供對圖形繪製的內置支持,而 R 語言卻對此提供了很好的支持。我們使用 R 語言的 svg 模塊繪製三角函數的三維散點圖。

我們可以使用 GraalVM 的 polyglot API,並將其他語言代碼的運行結果添加到 JavaScript 中。

在瀏覽器中打開 http://localhost:3000/ 查看結果。

圖片: https://images-cdn.shimo.im/LS2Lt9CD7NY0u7Dz/2.png!thumbnail 這是 GraalVM 的第三個用途——運行使用多種語言編寫的程序,並組合使用這些語言的模塊。我認為這是語言和模塊的大眾化——你可以為你的問題選擇任何一門合適的語言以及任何你想要的庫。

在 JVM 上運行本地語言

GraalVM 也支持 C 語言,GraalVM 可以像運行 JavaScript 和 Ruby 之類的語言一樣運行 C 代碼。

實際上,GraalVM 通過運行 LLVM 位碼的方式來支持 C 語言,而不是直接運行 C 代碼。也就是說,我們可以將現有工具與 C 語言一起使用,還可以使用其他可輸出 LLVM 的語言,例如 C++、Fortran 和未來可能出現的其他語言。為了簡化演示,我使用了由 Stephen McCamant 維護的 gzip 的單文件版本。為簡單起見,它只是將 gzip 源代碼和 autoconf 配置連成一個單獨的文件。我還需要修改一些東西才能讓它在 macOS 上運行起來,但不能在 GraalVM 上運行。

然後我們使用標準 clang(LLVM C 語言編譯器)來編譯它,並把它編譯成 LLVM 位碼,而不是本地彙編代碼,這樣就可以在 GraalVM 上運行。我使用的是 clang 4.0.1。

然後我們使用 lli 命令(LLVM 位碼解釋器)直接在 GraalVM 上運行編譯後的位碼。我們先使用系統 gzip 來壓縮文件,然後使用運行在 GraalVM 上的 gzip 進行解壓縮。

GraalVM 中的 Ruby 和 Python 實現也使用了這種技術來運行 C 擴展。也就是說,我們可以在虛擬機內部運行 C 擴展,同時保持高性能。

這是 GraalVM 的第四個用途——運行使用 C 和 C++ 等本地語言編寫的程序,並且還可以運行 C 語言擴展,而像 JRuby 這樣的 JVM 是無法做到這點的。

適用於所有編程語言的工具

如果你使用 Java 編程,可能已經習慣了使用那些高質量的工具,比如 IDE、調試器和分析器,但並非所有的編程語言都有這麼好用的工具。不過如果你是在 GraalVM 中使用某種語言,就可以獲得這樣的工具。

所有 GraalVM 語言(目前除了 Java)都是使用 Truffle 框架實現的,所以一個功能只要開發一次(比如調試器),就可以用在所有語言上。

為了試驗這個功能,我們開發了一個簡單的 FizzBuzz 程序。它將內容輸出到屏幕上,邏輯分支很清晰,只需要進行少量迭代,我們因此可以很容易地設置斷點。我們先使用 JavaScript 來實現。

我們可以像平常一樣使用 GraalVM 運行這個 JavaScript 程序。

我們也可以用 --inspect 標記來運行它,它會輸出一個可以在 Chrome 中打開的鏈接,並在調試器中暫停程序的運行。

然後我們在 FizzBuzz 代碼中設置一個斷點,並繼續執行。在跳過斷點後,我們可以看到 n 的值,然後繼續,或者查看調試介面的其餘部分。

這個標誌也可用在 Python、Ruby 和 R 語言上。我就不展示每個程序的源代碼了,它們的運行方式都是一樣的。

你可能對 Java 的另一個工具 VisualVM 很熟悉,它為我們提供了一個用戶界面,可以將它連接到本機或網路上的某個 JVM,以便檢查各種問題,比如內存和線程的使用情況。

GraalVM 也包含了帶有標準 jvisualvm 命令的 VisualVM。

如果我們在運行 TopTen Java 應用程序的同時運行 jvisualvm,就可以看到內存隨時間變化的情況,或者我們可以做一些其他的事情,比如進行堆轉儲,然後檢查堆中的對象類型。

我寫了一個用來生成垃圾的 Ruby 程序。

如果使用 VisualVM 來運行標準的 JVM 語言(如 JRuby),則會感到失望,因為你看到的是底層的 Java 對象,而不是所使用語言的對象的信息。

如果我們使用 Ruby 的 GraalVM 版本,VisualVM 將會識別出 Ruby 對象。我們需要使 --jvm 選項來啟動 VisualVM,因為它不支持本地版本的 Ruby。

如果有需要的話,我們還可以看到底層 Java 對象的堆轉儲,或者在 Summary 下選擇 Ruby Heap 來查看 Ruby 對象。

圖片: https://images-cdn.shimo.im/eNJCsYUOgoE11Yrc/8.png!thumbnailTruffle 框架就像是編程語言和工具之間的一種聯繫。如果我們使用 Truffle 來開發自己的編程語言,並基於 Truffle 的工具 API 開發各種工具(比如調試器),那麼開發出來的工具可以適用於每一種語言,而且只需要開發一次即可。

這是 GraalVM 的第五個用途——為編程語言提供高質量的工具。

擴展基於 JVM 的應用程序

如果我們使用了 GraalVM 的 javac 和 java 命令,那麼 org.graalvm…就已經存在於類路徑中,可以直接編譯並運行代碼,不需要使用任何額外的標記。

這些版本的語言與通過使用 node 和 ruby 這些命令運行的代碼一樣,都具備了很高的性能。

這是 GraalVM 的第六個用途——作為在 Java 應用程序中嵌入不同語言的介面。我們可以藉助 polyglot API 來獲取其他語言的對象,並將它們用作 Java 介面,實現其他複雜的操作。

結 論

GraalVM 支持多種新功能,它是一個平台,我們可以在這個平台上構建更強大的語言和工具,並將它們放入更多的環境中。無論程序在哪裡運行,或者使用了哪種語言,它都可以讓我們選擇所需的語言和模塊。

進階必備專欄:《深入拆解 Java 虛擬機》。掃碼,即可試讀此專欄的前三篇文章


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

引擎V8推出「並發標記」,可節省60%-70%的GC時間
港股上市!小米開源項目盤點

TAG:InfoQ |