溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

發(fā)布時(shí)間:2020-08-17 14:51:48 來(lái)源:ITPUB博客 閱讀:342 作者:千鋒Python唐小強(qiáng) 欄目:web開(kāi)發(fā)
后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

這是Java,Go和Rust之間的比較。這不是基準(zhǔn)測(cè)試,更多是對(duì)可執(zhí)行文件大小、內(nèi)存使用率、CPU使用率、運(yùn)行時(shí)要求等的比較,當(dāng)然還有一個(gè)小的基準(zhǔn)測(cè)試,可以看到每秒處理的請(qǐng)求數(shù)量,我將嘗試對(duì)這些數(shù)字進(jìn)行有意義的解讀。

為了嘗試將蘋(píng)果與蘋(píng)果進(jìn)行比較(也許是?),我在此比較中使用每種語(yǔ)言編寫(xiě)了一個(gè)Web服務(wù)。Web服務(wù)非常簡(jiǎn)單,它提供了三個(gè)REST服務(wù)端點(diǎn)(endpoint)。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

Web服務(wù)提供的服務(wù)端點(diǎn)

這三個(gè)Web服務(wù)的代碼倉(cāng)庫(kù)托管在github上。

編譯后的二進(jìn)制文件尺寸

有關(guān)如何構(gòu)建二進(jìn)制文件的一些信息。對(duì)于Java,我使用maven-shade-plugin和mvn package命令將所有內(nèi)容構(gòu)建到一個(gè)大的jar中。對(duì)于Go,我使用go build。最后,我使用了cargo build --release構(gòu)建Rust服務(wù)的二進(jìn)制文件。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

每個(gè)程序的大?。ㄒ哉鬃止?jié)為單位)

編譯后的文件大小還取決于所選的庫(kù)/依賴(lài)項(xiàng),因此,如果依賴(lài)項(xiàng)的身軀臃腫,則編譯后的程序也將難以幸免。在我的特定情況下,針對(duì)我選擇的特定庫(kù),以上是程序編譯后的大小。

在后續(xù)的一個(gè)單獨(dú)小節(jié)中,我會(huì)把這三個(gè)程序都構(gòu)建并打包為docker鏡像,并列出它們的大小,以顯示每種語(yǔ)言所需的運(yùn)行時(shí)開(kāi)銷(xiāo)。下面有更多詳細(xì)信息。

內(nèi)存使用情況

空閑狀態(tài)

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

每個(gè)應(yīng)用程序在內(nèi)存空閑時(shí)的內(nèi)存使用情況

什么?Go和Rust版本顯示空閑時(shí)內(nèi)存占用量的條形圖在哪里?好了,它們?cè)谀抢?,只有JVM啟動(dòng)的程序在空閑狀態(tài)時(shí)消耗160 MB以上的內(nèi)存,它什么也沒(méi)做。Go應(yīng)用程序僅使用0.86 MB,Rust應(yīng)用也僅使用了0.36 MB。這是一個(gè)巨大的差異!在這里,Java使用的內(nèi)存比Go和Rust應(yīng)用使用的內(nèi)存高出兩個(gè)數(shù)量級(jí),只是空占著內(nèi)存卻什么都不做。那是巨大的資源浪費(fèi)。

服務(wù)REST請(qǐng)求

讓我們使用wrk發(fā)起訪(fǎng)問(wèn)API的請(qǐng)求,并觀(guān)察內(nèi)存和CPU使用情況,以及在我的計(jì)算機(jī)上三個(gè)版本程序的每個(gè)端點(diǎn)每秒處理的請(qǐng)求數(shù)。

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

上面的wrk命令使用兩個(gè)線(xiàn)程并在連接池中保持400個(gè)打開(kāi)的連接,并重復(fù)調(diào)用GET端點(diǎn),持續(xù)30秒。這里我僅使用兩個(gè)線(xiàn)程,因?yàn)閣rk和被測(cè)程序都在同一臺(tái)計(jì)算機(jī)上運(yùn)行,所以我不希望它們?cè)诳捎觅Y源(尤其是CPU)上相互競(jìng)爭(zhēng)(太多)。

每個(gè)Web服務(wù)都經(jīng)過(guò)單獨(dú)測(cè)試,并且在每次運(yùn)行之間都重新啟動(dòng)了Web服務(wù)。

以下是該程序的每個(gè)版本的三個(gè)運(yùn)行中的最佳結(jié)果。

  • /hello

該端點(diǎn)返回Hello,World!信息。它分配字符串“ Hello,World!” 并將其序列化并以JSON格式返回。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/hello端點(diǎn)的CPU使用率

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/hello端點(diǎn)的內(nèi)存使用情況

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/hello端點(diǎn)處理的每秒請(qǐng)求數(shù)

  • /greeting/{name}

該端點(diǎn)接受一個(gè)段路徑參數(shù){name},然后格式化字符串“Hello,{name}!”,序列化并以JSON格式的問(wèn)候消息返回。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/greeting端點(diǎn)的CPU使用率

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/greeting端點(diǎn)的內(nèi)存使用情況

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/greeting端點(diǎn)處理的每秒請(qǐng)求數(shù)

  • /fibonacci/{number}

該端點(diǎn)接受一個(gè)段路徑參數(shù){number},并返回序列化為JSON格式的斐波納契數(shù)和輸入數(shù)。

對(duì)于這個(gè)特定的端點(diǎn),我選擇以遞歸形式實(shí)現(xiàn)它。我毫不懷疑,迭代實(shí)現(xiàn)會(huì)產(chǎn)生更好的性能結(jié)果,并且出于生產(chǎn)目的,應(yīng)該選擇一種迭代形式,但是在生產(chǎn)代碼中,有些情況下必須使用遞歸(并非專(zhuān)門(mén)用于計(jì)算第n個(gè)斐波那契數(shù) )。為此,我希望該實(shí)現(xiàn)涉及大量CPU棧分配。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/fibonacci端點(diǎn)的CPU使用率

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/fibonacci端點(diǎn)的內(nèi)存使用情況

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/fibonacci端點(diǎn)處理的每秒請(qǐng)求數(shù)

在Fibonacci端點(diǎn)測(cè)試期間,Java是唯一一個(gè)有150個(gè)請(qǐng)求超時(shí)的實(shí)現(xiàn),如下面wrk的輸出所示。

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

超時(shí)時(shí)間

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

/fibonacci端點(diǎn)的延遲

運(yùn)行時(shí)大小

為了模擬現(xiàn)實(shí)世界中的云原生應(yīng)用程序,并避免“它僅可以在我的機(jī)器上運(yùn)行!”,我分別為這三個(gè)應(yīng)用程序創(chuàng)建了一個(gè)docker鏡像。

Docker文件的源代碼包含在代碼庫(kù)相應(yīng)程序文件夾下。

作為我使用過(guò)的Java應(yīng)用程序的基礎(chǔ)鏡像,openjdk:8-jre-alpine是已知大小最小的鏡像之一,但是,這附帶了一些警告,這些警告可能適用于您的應(yīng)用程序,也可能不適用于您的應(yīng)用程序,主要是alpine鏡像在處理環(huán)境變量名稱(chēng)方面不是posix兼容的,因此您不能在Dockerfile中使用ENV中的(點(diǎn))字符(不過(guò)這沒(méi)什么大不了的),另一個(gè)是alpine Linux鏡像是使用musl libc而不是glibc編譯的,這意味著如果您的應(yīng)用程序依賴(lài)于需要glibc,它可能無(wú)法正常工作。不過(guò),在這里,alpine鏡像工作是正常的。

至于應(yīng)用程序的Go版本和Rust版本,我已經(jīng)對(duì)其進(jìn)行了靜態(tài)編譯,這意味著它們不希望在運(yùn)行時(shí)鏡像中存在libc(glibc,musl…等),這也意味著它們不需要運(yùn)行OS的基本鏡像。因此,我使用了scratch docker鏡像,這是一個(gè)no-op鏡像,以零開(kāi)銷(xiāo)托管已編譯的可執(zhí)行文件。

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

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

最終Docker鏡像大小

結(jié)論

后端程序員一定要看的語(yǔ)言大比拼:Java vs. Go vs. Rust

三種語(yǔ)言的比較

在得出任何結(jié)論之前,我想指出這三種語(yǔ)言之間的關(guān)系。Java和Go都是支持垃圾回收的語(yǔ)言,但是Java會(huì)提前編譯為在JVM上運(yùn)行的字節(jié)碼。啟動(dòng)Java應(yīng)用程序時(shí),JIT編譯器會(huì)被調(diào)用以通過(guò)將字節(jié)碼編譯為本地代碼來(lái)優(yōu)化字節(jié)碼,以提高應(yīng)用程序的性能。

Go和Rust都提前編譯為本地代碼,并且在運(yùn)行時(shí)不會(huì)進(jìn)行進(jìn)一步的優(yōu)化。

Java和Go都是支持垃圾收集的語(yǔ)言,具有**STW(停止世界)**的副作用。這意味著,每當(dāng)垃圾收集器運(yùn)行時(shí),它將停止應(yīng)用程序,進(jìn)行垃圾收集,并在完成后從停止的地方恢復(fù)應(yīng)用程序。大多數(shù)垃圾收集器需要停止運(yùn)行,但是有些實(shí)現(xiàn)似乎不需要這樣做。

當(dāng)Java語(yǔ)言在90年代創(chuàng)建時(shí),其最大的賣(mài)點(diǎn)之一是 一次編寫(xiě),可在任何地方運(yùn)行。當(dāng)時(shí)這非常好,因?yàn)槭袌?chǎng)上沒(méi)有很多虛擬化解決方案。如今,大多數(shù)CPU支持虛擬化,這種虛擬化抵消了使用某種語(yǔ)言進(jìn)行開(kāi)發(fā)的誘惑(該語(yǔ)言承諾可以運(yùn)行在任何平臺(tái)上)。Docker和其他解決方案以更為低廉的代價(jià)提供虛擬化。

在整個(gè)測(cè)試中,應(yīng)用程序的Java版本比Go或Rust對(duì)應(yīng)版本消耗了更多的內(nèi)存,在前兩個(gè)測(cè)試中,Java使用的內(nèi)存大約增加了8000%。這意味著對(duì)于實(shí)際應(yīng)用程序,Java應(yīng)用程序的運(yùn)行成本會(huì)更高。

對(duì)于前兩個(gè)測(cè)試,Go應(yīng)用程序使用的CPU比Java少20%,同時(shí)處理比java版多出38%的請(qǐng)求。另一方面,Rust版本使用的CPU比Go減少了57%,而處理的請(qǐng)求卻增加了13%。

第三次測(cè)試在設(shè)計(jì)上是占用大量CPU的資源,因此我想從中擠出CPU的每一分。Go和Rust都比Java多使用了1%的CPU。而且我認(rèn)為,如果wrk不是在同一臺(tái)計(jì)算機(jī)上運(yùn)行,那么這三個(gè)版本都會(huì)使CPU達(dá)到100%的上限值。在內(nèi)存方面,Java使用的內(nèi)存比Go和Rust多2000%。Java可以處理的請(qǐng)求比Go多出20%,而Rust可以處理的請(qǐng)求比Java多出15%。

在撰寫(xiě)本文時(shí),Java編程語(yǔ)言已經(jīng)存在了將近30年,這使得在市場(chǎng)上尋找Java開(kāi)發(fā)人員變得相對(duì)容易。另一方面,Go和Rust都是相對(duì)較新的語(yǔ)言,因此與Java相比,自然而然的開(kāi)發(fā)人員的數(shù)量更少些。不過(guò),Go和Rust都擁有很大的吸引力,許多開(kāi)發(fā)人員正在將它們用于新項(xiàng)目,并且有許多使用Go和Rust的生產(chǎn)中正在運(yùn)行的項(xiàng)目,因?yàn)楹?jiǎn)單地說(shuō),就資源而言,它們比Java更有效。

在編寫(xiě)本文的程序時(shí),我同時(shí)學(xué)習(xí)了Go和Rust。就我而言,Go的學(xué)習(xí)曲線(xiàn)很短,因?yàn)樗且环N相對(duì)容易掌握的語(yǔ)言,并且與其他語(yǔ)言相比語(yǔ)法很小。我只用了幾天就用Go編寫(xiě)了程序。關(guān)于Go需要注意的一件事是編譯速度,我不得不承認(rèn),與Java/C/C++/Rust等其他語(yǔ)言相比,它的速度非???。該程序的Rust版本花了我大約一個(gè)星期的時(shí)間來(lái)完成,我不得不說(shuō),大部分時(shí)間都花在弄清borrow checker向我要什么上。Rust具有嚴(yán)格的所有權(quán)規(guī)則,但是一旦掌握了Rust的所有權(quán)和借用概念,編譯器錯(cuò)誤消息就會(huì)突然變得更加有意義。違反借閱檢查規(guī)則時(shí),Rust編譯器對(duì)您大吼的原因是因?yàn)榫幾g器希望在編譯時(shí)證明已分配內(nèi)存的壽命和所有權(quán)。這樣做可以保證程序的安全性(例如:沒(méi)有懸掛的指針,除非使用了不安全(unsafe)的代碼逃離檢查),并且在編譯時(shí)確定了釋放位置,從而消除了垃圾收集器的需求和運(yùn)行時(shí)成本。當(dāng)然,這是以學(xué)習(xí)Rust的所有權(quán)系統(tǒng)為代價(jià)的。

在競(jìng)爭(zhēng)方面,我認(rèn)為Go是Java(通常是JVM語(yǔ)言)的直接競(jìng)爭(zhēng)對(duì)手,但不是Rust的競(jìng)爭(zhēng)對(duì)手。另一方面,Rust是Java,Go,C和C ++的重要競(jìng)爭(zhēng)對(duì)手。

由于他們的效率,我看到了自己將會(huì)在Go和Rust中編寫(xiě)更多的程序,但是很可能在Rust中編寫(xiě)更多的程序。兩者都非常適合Web服務(wù),CLI,系統(tǒng)程序(…etc)開(kāi)發(fā)。但是,Rust比Go具有根本優(yōu)勢(shì)。它不是垃圾收集的語(yǔ)言,與C和C++相比,它可以安全地編寫(xiě)代碼。例如,Go并不是特別適合用于編寫(xiě)OS內(nèi)核,而這里又是Rust的亮點(diǎn),并與C/C ++競(jìng)爭(zhēng),因?yàn)樗鼈兪鞘褂肙S編寫(xiě)的長(zhǎng)期存在和事實(shí)上的語(yǔ)言。Rust與C/C++競(jìng)爭(zhēng)的另一種方式在嵌入式世界中,我將繼續(xù)進(jìn)行討論。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI