Go、Java 和 Rust 的比較:得出了挺多結論

Go、Java 和 Rust 的比較:得出了挺多結論

本文是 Java,Go 和 Rust 之間的比較。這不是基準測試,而是更多關注輸出可執行文件大小,內存使用,CPU 使用率,運行時要求之間的比較,當然還有一個小的基準測試,可以每秒獲取一些請求,並嘗試展示一些數據。

為了嘗試將蘋果與蘋果進行比較(也許是?),我在此比較中使用每種語言編寫了一個 Web 服務。Web 服務非常簡單,它為三個 REST 端點提供服務。

Go、Java 和 Rust 的比較:得出了挺多結論

Web服務在Java,Go和Rust中提供服務的端點

這三個 Web 服務的代碼託管在 GitHub: http://github.com/dexterdarwich/ws-compare 上。

可執行文件大小

有關如何構建二進制文件的一些信息。在 Java 中,我已經使用 maven-shade-plugin 將所有內容構建到一個大的 jar 中,並使用了 mvn packagetarget。對於 Go,我使用 go build。而對於 Rust,我使用 build --release。

Go、Java 和 Rust 的比較:得出了挺多結論

每個程序的編譯大小(以 Mb 為單位)

編譯後的大小還取決於所選的庫/依賴項,因此,如果它們大,則編譯後的程序將最終相同。在我的特定情況下,對於我選擇的庫,以上是程序的編譯大小。

在後面單獨的部分,我將把這三個程序構建並打包為 Docker 映像,並列出它們的大小,以顯示每種語言所需的運行時開銷。下面有更多詳細信息。

內存使用

空閒時的內存使用

Go、Java 和 Rust 的比較:得出了挺多結論

每個應用程序在內存空閒時的內存使用情況

什麼?Go 和 Rust 版本的條形圖在哪裡顯示空閒時的內存佔用量?好了,它們在那裡,只有當 JVM 啟動程序並處於空閒狀態時,Java 才消耗 160 MB 以上的內存,這時什麼也沒做。對於 Go,程序使用 0.86 MB,對於Rust,則使用 0.36 MB。這是一個很大的差異!在這裡,Java 使用的內存比 Go 和 Rust 對應的內存高兩個數量級,什麼都沒做就耗這麼多內存,是巨大的資源浪費。

服務 Rest 請求

讓我們使用 wrk 通過請求訪問 API,並觀察內存和 CPU 使用情況,以及在我的計算機上針對程序的三個版本的每個端點每秒實現的請求數。

<code>wrk -t2 -c400 -d30s http://127.0.0.1:8080/hello 
wrk -t2 -c400 -d30s http://127.0.0.1:8080/greeting/Jane
wrk -t2 -c400 -d30s http://127.0.0.1:8080/fibonacci/35/<code>

上面的 wrk 命令表示:使用兩個線程(用於 wrk)並在池中保留 400 個打開的連接,並重復使用 GET 方式訪問端點,持續 30 秒。這裡我僅使用兩個線程,因為 wrk 和被測程序都在同一臺計算機上運行,所以我不希望它們在可用資源(尤其是 CPU)上相互競爭(太多)。

每個 Web 服務都經過單獨測試,並且在每次運行之間都重新啟動了 Web 服務。

以下是該程序的每個版本的三個運行中的最佳結果。

/hello

該端點返回 Hello,World!信息。它生成字符串 “ Hello,World!” 將其序列化並以 JSON 格式返回。

Go、Java 和 Rust 的比較:得出了挺多結論

訪問hello端點時的CPU使用率

Go、Java 和 Rust 的比較:得出了挺多結論

請求hello端點時的內存使用情況

Go、Java 和 Rust 的比較:得出了挺多結論

請求hello端點時的每秒請求數

/greeting/{name}

該端點接受路徑參數{name},然後格式化字符串 “ Hello,{name}!”,進行序列化並將其返回為 JSON 格式的問候消息。

Go、Java 和 Rust 的比較:得出了挺多結論

請求greeting端點時的CPU使用率

Go、Java 和 Rust 的比較:得出了挺多結論

請求greeting端點時的內存使用情況

Go、Java 和 Rust 的比較:得出了挺多結論

請求greeting端點時的每秒請求數

/fibonacci/{number}

該端點接受路徑路參數 {number},並返回斐波納契數列並序列化為 JSON 格式。

這個特定的端點我選擇以遞歸形式實現它。毫不懷疑,循環方式實現會產生更好的性能結果,並且出於生產目的,應該選擇一種迭代形式,但是在生產代碼中,有些情況下必須使用遞歸(並非專門用於計算第 n 個斐波那契數))。因此,我希望實現大量涉及 CPU、棧內存分配。

Go、Java 和 Rust 的比較:得出了挺多結論

請求fibonacci端點時的CPU使用率

Go、Java 和 Rust 的比較:得出了挺多結論

請求fibonacci端點時的內存使用情況

Go、Java 和 Rust 的比較:得出了挺多結論

請求fibonacci端點時的每秒請求數

在 Fibonacci 端點測試中,Java 實現是唯一一個有 150 個請求出現了超時, wrk 的輸出如下所示。

Go、Java 和 Rust 的比較:得出了挺多結論

Go、Java 和 Rust 的比較:得出了挺多結論

fibonacci端點的時延

運行時大小

為了模擬現實世界中的雲原生應用程序,並消除“它可以在我的機器上運行!”,我為這三個應用程序中的每一個創建了一個 Docker 映像。

Docker 文件的源包含在相應程序文件夾下的代碼庫中。

作為 Java 應用程序的基本運行時鏡像,我使用了 openjdk:8-jre-alpine,該鏡像被稱為是最小的鏡像之一,但是,它附帶了一些警告,這些警告可能適用於你的應用程序,也可能不適用於你的應用程序,主要是 alpine 鏡像在處理環境變量名稱方面不符合 posix,因此在 Docker 文件中,你不能使用 . 字符,另一方面是 alpine Linux 鏡像是使用 musl libc 而不是 glibc 編譯的,這意味著如果你的應用程序依賴需要 glibc,它將無法正常工作。在我這個例子,alpine 可以正常運行。

至於應用程序的 Go 版本和 Rust 版本,我已經對其進行了靜態編譯,這意味著它們不希望在運行時鏡像中存在libc(glibc,musl…等),這也意味著它們不需要運行 OS 的基本映像。因此,我使用了臨時 docker 映像,這是一個無操作映像,以零開銷託管已編譯的可執行文件。

我使用的 Docker 映像的命名約定為 {lang}/webservice。該應用程序的 Java,Go 和 Rust 版本的鏡像大小分別為 113、8.68 和 4.24 MB。

Go、Java 和 Rust 的比較:得出了挺多結論

最終Docker映像大小

結論

Go、Java 和 Rust 的比較:得出了挺多結論

三種語言的比較

在得出任何結論之前,我想指出這三種語言之間的關係。Java 和 Go 都是垃圾收集語言,但是 Java 會提前編譯(AOT)為在 JVM 上運行的字節碼。當啟動 Java 應用程序時,即時(JIT)編譯器將被調用,以通過隨時隨地將其編譯為本機代碼來優化字節碼,以提高應用程序的性能。

Go 和 Rust 都提前編譯為本地代碼,並且在運行時不會進行進一步的優化。

Java 和 Go 都是垃圾收集語言,有 Stop-The-World 的副作用。這意味著,每當垃圾收集器運行時,它將停止應用程序,進行垃圾收集,並在完成後從停止的地方恢復應用程序。大多數垃圾收集器需要停止運行,但是有些實現似乎不需要這樣做。

Java 語言是 90 年代創建的,其最大的賣點之一是一次編寫,可在任何地方運行。當時,這很棒,因為市場上沒有很多虛擬化解決方案。如今,各種虛擬化技術存在,特別是 Docker 和其他解決方案以便宜的價格提供虛擬化,使得跨平臺不再那麼稀奇。

在整個測試中,應用程序的 Java 版本比 Go 或 Rust 對應版本消耗了更多的內存,在前兩個測試中,Java 使用的內存大約增加了 8000%。這意味著對於實際應用程序,Java 應用程序的運行成本會更高。

對於前兩個測試,Go 應用程序使用的 CPU 比 Java 少 20%,而處理的請求卻增加 38%。另一方面,Rust 版本使用的 CPU 比 Go 減少了 57%,而處理的請求卻增加了 13%。

第三次測試在設計上是佔用大量 CPU 的資源,因此我想從中壓榨 CPU。Go 和 Rust 都比 Java 多使用了 1% 的 CPU。而且我認為,如果 wrk 不在同一臺計算機上運行,則所有這三個版本都會使 CPU 上限達到 100%。在內存方面,Java 使用的內存比 Go 和 Rust 多 2000%。Java 可以處理的請求比 Go 多出 20%,而 Rust 可以處理的請求比 Java 多出 15%。

在撰寫本文時,Java 編程語言已經存在了將近 30 年,這使得在市場上尋找 Java 開發人員變得相對容易。另一方面,Go 和 Rust 都是相對較新的語言,因此與 Java 相比,自然而然開發人員更少。不過,Go 和 Rust 都獲得了很大的吸引力,許多開發人員正在將它們用於新項目,並且有許多在生產環境使用 Go 和 Rust 的項目或公司,因為簡單地說,就資源而言,它們比 Java 更有效。(也許是因為它們是新的、酷的語言!)

在編寫本文的程序時,我同時學習了 Go 和 Rust。就我而言,Go 的學習曲線很平滑,因為它是一種相對容易掌握的語言,並且與其他語言相比語法很少。我只用了幾天就用 Go 編寫了程序。關於 Go 需要注意的一件事是編譯速度,我不得不承認,與 Java/C/C++/Rust 等其他語言相比,它的速度非常快。該程序的 Rust 版本花了我大約一個星期的時間來完成,我不得不說,大部分時間都花在弄清借閱檢查器(borrow checker)向我要什麼上。Rust 具有嚴格的所有權規則,但是一旦掌握了 Rust 的所有權和借用(borrowing)概念,編譯器錯誤消息就會突然變得更加有意義。當違反借閱檢查規則時,Rust 編譯器對您大吼大叫的原因是,因為編譯器要在編譯時證明已分配內存的壽命和所有權。這樣,它保證了程序的安全性(例如:除非使用了不安全的代碼轉義,否則就沒有懸掛指針),並且在編譯時確定了釋放位置,從而消除了垃圾收集器的需求和運行時成本。當然,這是以學習 Rust 的所有權系統為代價的。

在競爭方面,我認為 Go 是 Java(通常是 JVM 語言)的直接競爭對手,但不是 Rust 的競爭對手。另一方面,Rust 是Java,Go,C 和 C++ 的重要競爭對手。(polaris 注:感覺 Rust 好猛呀!)

由於它們的效率,我想到了自己。並且將會用 Go 和 Rust 編寫更多的程序,但是很可能用 Rust 編寫更多的程序。兩者都非常適合網絡服務,CLI,系統程序等的開發。但是,Rust 比 Go 具有根本優勢。它不是垃圾收集的語言,與 C 和 C++ 相比,它可以安全地編寫代碼。例如,Go 並不是特別適合用於編寫 OS 內核,而這裡又是 Rust 的亮點,並與 C/C++ 競爭,因為它們是使用 OS 編寫的長期存在和事實上的語言。Rust 與 C/C++ 競爭的另一種方式是在嵌入式世界中,但我將後續進行討論。

感覺你的閱讀!

原文鏈接:http://medium.com/@dexterdarwich/comparison-between-java-go-and-rust-fdb21bd5fb7c

作者:Dexter Darwich

翻譯:polaris


分享到:


相關文章: