新聞中心
Rust越來越受歡迎。因此,不管Rust是否對(duì)我們都具有戰(zhàn)略意義,包括我自己在內(nèi)的一組同事對(duì)其進(jìn)行了為期半天的評(píng)估,以建立我們自己的觀點(diǎn)。我們按照標(biāo)準(zhǔn)入門書進(jìn)行了一些編碼,查看了一些框架,并觀看了“ Considering Rust”演示文稿。。總的結(jié)論大致是這樣的:是的,一種不錯(cuò)的新編程語言,但是沒有一個(gè)成熟的生態(tài)系統(tǒng),也沒有任何垃圾收集,對(duì)于我們的項(xiàng)目而言,這將是太麻煩和無用的。我的直覺與關(guān)于垃圾收集的評(píng)估不一致。因此,我做了一些進(jìn)一步的挖掘和測(cè)試,并得出了當(dāng)前的結(jié)論:Rust確實(shí)進(jìn)行了垃圾收集,但是使用的是非常聰明的方式。

成都創(chuàng)新互聯(lián)公司主要從事做網(wǎng)站、網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)曾都,十余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
垃圾收集簡史
當(dāng)您查看Rust的網(wǎng)站并閱讀介紹時(shí),您會(huì)突然發(fā)現(xiàn)一個(gè)驕傲的聲明,Rust沒有垃圾收集器。如果您與我同齡,這會(huì)引起一些不好的回憶。有時(shí)候,您必須使用手動(dòng)分配內(nèi)存,malloc()然后稍后再釋放它。如果過早釋放它,則會(huì)遇到諸如無效的內(nèi)存訪問異常之類的攻擊。如果忘記釋放它,則會(huì)造成內(nèi)存泄漏,從而使應(yīng)用程序受阻。很少有人在第一次就做對(duì)。這根本沒什么好玩的。
在研究Rust采取的方法之前,讓我們簡短地看看垃圾的實(shí)際含義。在Wikipedia中,有一個(gè)很好的定義:垃圾包括數(shù)據(jù)………在其上運(yùn)行的程序在以后的任何計(jì)算中都不會(huì)使用。這意味著只有開發(fā)人員才能決定是否可以釋放存儲(chǔ)某些數(shù)據(jù)的內(nèi)存段。但是,應(yīng)用程序的運(yùn)行時(shí)可以自動(dòng)檢測(cè)垃圾的子集。如果在某個(gè)時(shí)間點(diǎn)不再存在對(duì)內(nèi)存段的引用,則程序?qū)o法訪問該段。并且,因此可以安全地刪除它。
為了實(shí)際實(shí)現(xiàn)這種支持,運(yùn)行時(shí)必須分析應(yīng)用程序中的所有活動(dòng)引用,并且必須檢查所有已分配的內(nèi)存引用(如果可以針對(duì)當(dāng)前應(yīng)用程序狀態(tài)訪問它們)。這是一項(xiàng)計(jì)算量很大的任務(wù)。在Java誕生的第一天,JVM突然凍結(jié),不得不在相當(dāng)長的時(shí)間內(nèi)進(jìn)行垃圾回收。如今,有很多用于垃圾收集的復(fù)雜算法,它們通常與應(yīng)用程序同時(shí)運(yùn)行。但是,計(jì)算復(fù)雜度仍然相同。
從好的方面來說,應(yīng)用程序開發(fā)人員無需考慮手動(dòng)釋放內(nèi)存段。永遠(yuǎn)不會(huì)有無效的內(nèi)存訪問異常。她仍然可以通過引用數(shù)據(jù)來創(chuàng)建內(nèi)存泄漏,現(xiàn)在不再需要。(恕我直言,主要的示例是自寫的緩存實(shí)現(xiàn)。老人的建議:切勿使用ehcache之類的方法。)但是,隨著垃圾收集器的引入,內(nèi)存泄漏的情況越來越少了。
Rust如何處理內(nèi)存段
乍一看,Rust看起來很像C,尤其是其引用和取消引用。但是它具有處理內(nèi)存的獨(dú)特方法。每個(gè)內(nèi)存段均由一個(gè)引用擁有。從開發(fā)人員的角度來看,始終只有一個(gè)變量擁有數(shù)據(jù)。如果此變量超出范圍且不再可訪問,則將所有權(quán)轉(zhuǎn)移到其他變量或釋放內(nèi)存。
使用這種方法,不再需要計(jì)算所有數(shù)據(jù)的可達(dá)性。取而代之的是,每次關(guān)閉命名上下文時(shí)(例如,通過從函數(shù)調(diào)用返回),都會(huì)使用簡單的算法來驗(yàn)證所用內(nèi)存的可訪問性。聽起來好極了,以至于每個(gè)有經(jīng)驗(yàn)的開發(fā)人員都可能立即想到一個(gè)問題:問題在哪里?
問題在于,開發(fā)人員必須照顧所有權(quán)。開發(fā)人員不必在整個(gè)應(yīng)用程序中漫不經(jīng)心地散布對(duì)數(shù)據(jù)的引用,而必須標(biāo)記所有權(quán)。如果所有權(quán)沒有明確定義,則編譯器將打印錯(cuò)誤并停止工作。
為了進(jìn)行評(píng)估,與傳統(tǒng)的垃圾收集器相比,該方法是否真的有用,我看到兩個(gè)問題:
- 開發(fā)人員在開發(fā)時(shí)標(biāo)記所有權(quán)有多難?如果她所有的精力都集中在與編譯器進(jìn)行斗爭(zhēng)而不是解決域問題上,那么這種方法所帶來的好處遠(yuǎn)不止于幫助。
- 與傳統(tǒng)的垃圾收集器相比,Rust解決方案的速度快多少?如果收益不大,那為什么還要打擾呢?
為了回答這兩個(gè)問題,我在Rust和Kotlin中執(zhí)行了一項(xiàng)任務(wù)。該任務(wù)對(duì)于企業(yè)環(huán)境而言是典型的,會(huì)產(chǎn)生大量垃圾。第一個(gè)問題是根據(jù)我的個(gè)人經(jīng)驗(yàn)和觀點(diǎn)回答的,第二個(gè)是通過具體測(cè)量得出的。
任務(wù):處理數(shù)據(jù)庫
我選擇的任務(wù)是模擬典型的以數(shù)據(jù)庫為中心的任務(wù),計(jì)算所有員工的平均收入。每個(gè)員工都被加載到內(nèi)存中,并且平均值會(huì)循環(huán)計(jì)算。我知道您絕對(duì)不應(yīng)在現(xiàn)實(shí)生活中這樣做,因?yàn)閿?shù)據(jù)庫可以自己更快地完成此任務(wù)。但是,首先,我看到這種情況在現(xiàn)實(shí)生活中經(jīng)常發(fā)生,其次,對(duì)于某些NoSQL數(shù)據(jù)庫,您必須在應(yīng)用程序中執(zhí)行此操作,其次,這只是一些代碼,用于創(chuàng)建大量需要收集的垃圾。
我選擇了JVM上的Kotlin作為基于垃圾收集的編程語言的代表。JVM具有高度優(yōu)化的垃圾收集器,如果您習(xí)慣Kotlin,則使用Java就像在石器時(shí)代工作一樣。
您可以在GitHub上找到代碼:https://github.com/akquinet/GcRustVsJvm
用Kotlin處理
計(jì)算得出一系列員工,總結(jié)他們的薪水,計(jì)算員工數(shù)量,最后除以這些數(shù)字:
- fun computeAverageIncomeOfAllEmployees(
- employees : Sequence
- ) : Double
- {
- val (nrOfEmployees, sumOfSalaries) = employees
- .fold(Pair(0L, 0L),
- { (counter, sum), employee ->
- Pair(counter + 1, sum + employee.salary)
- })
- return sumOfSalaries.toDouble() /
- nrOfEmployees.toDouble()
- }
這里沒什么令人興奮的。(您可能會(huì)注意到一種函數(shù)式編程風(fēng)格。這是因?yàn)槲曳浅O矚g函數(shù)式編程。但這不是本文的主題。)垃圾是在創(chuàng)建雇員時(shí)創(chuàng)建的。我在這里創(chuàng)建隨機(jī)雇員,以避免使用真實(shí)的數(shù)據(jù)庫。但是,如果您使用JPA,則將具有相同數(shù)量的對(duì)象創(chuàng)建。
- fun lookupAllEmployees(
- numberOfAllEmployees : Long
- ): Sequence
- {
- return (1L..numberOfAllEmployees)
- .asSequence()
- .map { createRandomEmployee() }
- }
隨機(jī)對(duì)象的創(chuàng)建也非常簡單。字符串是從字符列表創(chuàng)建的charPool。
- fun createRandomEmployee(): Employee =
- Employee(
- createRandomStringOf80Chars(),
- createRandomStringOf80Chars(),
- ... // code cut Out
- )
- fun createRandomStringOf80Chars() =
- (1..80)
- .map { nextInt(0, charPool.size) }
- .map(charPool::get)
- .joinToString("")
Rust版本的一個(gè)小驚喜是我必須如何處理前面提到的字符列表。因?yàn)橹恍枰粋€(gè)單例,所以將其存儲(chǔ)在一個(gè)伴隨對(duì)象中。這里是它的輪廓:
- class EmployeeServices {
- companion object {
- private val charPool: List
- = ('a'..'z') + ('A'..'Z') + ('0'..'9')
- fun lookupAllEmployees(...) ...
- fun createRandomEmployee(): Employee ...
- fun computeAverageIncomeOfAllEmployees(...) ...
- }
- }
現(xiàn)在,以Rust方式處理
我偶然發(fā)現(xiàn)的第一件事是,將這個(gè)單例字符列表放在何處。Rust支持直接嵌入二進(jìn)制文件中的靜態(tài)數(shù)據(jù)和可以由編譯器內(nèi)聯(lián)的常量數(shù)據(jù)。兩種選擇僅支持一小部分表達(dá)式來計(jì)算單例的值。我計(jì)算允許的字符池的解決方案是這樣的:
- let char_pool = ('a'..'z').collect::>();
由于向量的計(jì)算基于類型推斷,因此無法將其指定為常量或靜態(tài)。我目前的理解是,Rust的慣用方法是添加功能需要處理的所有對(duì)象作為參數(shù)。因此,用于計(jì)算Rust中平均工資的主要調(diào)用如下所示:
- let average =
- compute_average_income_of_all_employees(
- lookup_all_employees(
- nr_of_employees, &char_pool,
- ) );
通過這種方法,所有依賴項(xiàng)都變得清晰了。具有C經(jīng)驗(yàn)的開發(fā)人員會(huì)立即認(rèn)識(shí)到地址運(yùn)算符&,該運(yùn)算符將內(nèi)存地址作為指針返回,并且是高效且可能無法維護(hù)的代碼的基礎(chǔ)。當(dāng)我的許多同事與Rust一起玩時(shí),這種基于C的負(fù)面體驗(yàn)被投射到Rust。
我認(rèn)為這是不公平的。C的&運(yùn)算符設(shè)計(jì)帶來的問題是,始終存在不可預(yù)測(cè)的副作用,因?yàn)閼?yīng)用程序的每個(gè)部分都可以存儲(chǔ)指向存儲(chǔ)塊的指針。另外,每個(gè)部分都可以釋放內(nèi)存,從而可能導(dǎo)致所有其他部分引發(fā)異常。
在Rust中,&操作員的工作方式有所不同。每個(gè)數(shù)據(jù)始終由一個(gè)變量擁有。如果使用&此所有權(quán)創(chuàng)建了對(duì)數(shù)據(jù)的引用,則該所有權(quán)將轉(zhuǎn)移到引用范圍內(nèi)。只有所有者可以訪問數(shù)據(jù)。如果所有者超出范圍,則可以釋放數(shù)據(jù)。
在我們的示例中,char_pool使用&運(yùn)算符將的所有權(quán)轉(zhuǎn)移到函數(shù)的參數(shù)。當(dāng)該函數(shù)返回時(shí),所有權(quán)將歸還給變量char_pool。因此,它是一種類似于C的地址運(yùn)算符,但它增加了所有權(quán)的概念,從而使代碼更簡潔。
Rust中的域邏輯
Rust的主要功能看起來與Kotlin差不多。由于隱含的數(shù)字類型,例如f6464位浮點(diǎn)數(shù),因此感覺有點(diǎn)基本。但是,這是您很快就會(huì)習(xí)慣的事情。
- fn compute_average_income_of_all_employees(
- employees: impl Iterator
- ) -> f64
- {
- let (num_of_employees, sum_of_salaries) =
- employees.fold(
- (0u64, 0u64),
- |(counter, sum), employee| {
- return (counter + 1,
- sum + employee.salary);
- });
- return (sum_of_salaries as f64) /
- (num_of_employees as f64);
- }
恕我直言,這是一個(gè)很好的例子,可以證明Rust是一種非常現(xiàn)代的干凈編程語言,并且對(duì)函數(shù)式編程風(fēng)格提供了很好的支持。
在Rust中創(chuàng)建垃圾
現(xiàn)在讓我們看一下程序的一部分,其中創(chuàng)建了許多對(duì)象,以后需要收集這些對(duì)象:
- fn lookup_all_employees<'a>(
- number_of_all_employees: u64,
- char_pool: &'a Vec
- ) -> impl Iterator
- + 'a
- {
- return
- (0..number_of_all_employees)
- .map(move | _ | {
- return create_random_employee(char_pool);
- })
- .into_iter();
- }
乍一看,這看起來很像Kotlin。它使用相同的功能樣式在循環(huán)中創(chuàng)建隨機(jī)雇員。返回類型是Iterator,類似于Kotlin中的序列,它是一個(gè)延遲計(jì)算的列表。
從第二個(gè)角度看,這些類型看起來很奇怪。這到底是'a什么?解決了懶惰評(píng)估的問題。由于Rust編譯器無法知道何時(shí)實(shí)際評(píng)估返回值,并且返回值取決于借入的引用,因此現(xiàn)在存在確定何時(shí)char_pool可以釋放借入值的問題。的'a注釋指定的壽命c(diǎn)har_pool必須至少只要是作為返回值的壽命。
對(duì)于習(xí)慣了經(jīng)典垃圾回收的開發(fā)人員來說,這是一個(gè)新概念。在Rust中,她有時(shí)必須明確指定對(duì)象的生存期。垃圾收集器進(jìn)行所有清理時(shí)不需要的東西。
第三,您可以發(fā)現(xiàn)move關(guān)鍵字。它強(qiáng)制閉包獲取其使用的所有變量的所有權(quán)。由于char_pool(再次),這是必要的。Map是延遲執(zhí)行的,因此,從編譯器的角度來看,閉包可能會(huì)超出變量的壽命c(diǎn)har_pool。因此,關(guān)閉必須擁有它的所有權(quán)。
其余代碼非常簡單。這些結(jié)構(gòu)是根據(jù)隨機(jī)創(chuàng)建的字符串創(chuàng)建的:
- fn create_random_employee(
- char_pool: &Vec
- ) -> Employee
- {
- return Employee {
- first_name:
- create_random_string_of_80_chars(char_pool),
- last_name:
- create_random_string_of_80_chars(char_pool),
- address: Address
- { // cut out .. },
- salary: 1000,
- };
- }
- fn create_random_string_of_80_chars(
- char_pool: &Vec
- ) -> String
- {
- return (0..80)
- .map(|_| {
- char_pool[
- rand::thread_rng()
- .gen_range(0,
- char_pool.len())]
- })
- .into_iter().collect();
- }
那么,Rust有多難?
實(shí)施這個(gè)微小的測(cè)試程序非常復(fù)雜。Rust是一種現(xiàn)代的編程語言,使開發(fā)人員能夠快速干凈地維護(hù)代碼。但是,它的內(nèi)存管理概念直接體現(xiàn)在語言的所有元素中,這是開發(fā)人員必須理解的。
工具支持很好,恕我直言。大多數(shù)時(shí)候,您只需要執(zhí)行編譯器告訴您的操作即可。但是有時(shí)您必須實(shí)際決定要如何處理數(shù)據(jù)。
現(xiàn)在,值得嗎?
即使聽起來有些令人信服,但我還是非常樂于做一些測(cè)量,看看現(xiàn)實(shí)是否也令人信服。因此,我為四個(gè)不同的輸入大小運(yùn)行了Rust和Kotlin應(yīng)用程序,測(cè)量了時(shí)間,并將結(jié)果放在對(duì)數(shù)比例圖中:
看著這些數(shù)字,我長了很長的臉。銹總是較慢;對(duì)于10 ^ 6個(gè)元素,一個(gè)非常糟糕的因子是11。這不可能。我檢查了代碼,沒有發(fā)現(xiàn)錯(cuò)誤。然后,我檢查了優(yōu)化情況,并發(fā)現(xiàn)了--release從dev模式切換到的標(biāo)志prod?,F(xiàn)在,結(jié)果看起來好多了:
這樣好多了?,F(xiàn)在,Rust總是比Kotlin快,并提供線性性能。在Kotlin上,我們看到了較長時(shí)間運(yùn)行的代碼的典型性能改進(jìn),這可能是由于及時(shí)編譯引起的。從10 ^ 4的輸入大小來看,Rust大約比Kotlin快3倍??紤]到JVM的成熟以及過去幾十年在基礎(chǔ)架構(gòu)上投入的資源,這是非常令人印象深刻的(Java的第一版于1995年發(fā)布)。
對(duì)于我來說,令人驚訝的是與生產(chǎn)配置文件相比,開發(fā)配置文件的速度要慢得多。40的系數(shù)是如此之大,以至于您永遠(yuǎn)都不應(yīng)將開發(fā)配置文件用于發(fā)行。
結(jié)論
Rust是一種現(xiàn)代編程語言,具有您如今已習(xí)慣的所有舒適性。它具有一種新的內(nèi)存處理方法,這給開發(fā)人員帶來了一點(diǎn)額外負(fù)擔(dān),同時(shí)還提供了出色的性能。
而且,要回答標(biāo)題的最初問題,您不必手動(dòng)處理Rust中的垃圾。此垃圾收集由運(yùn)行時(shí)系統(tǒng)完成,但現(xiàn)在不再稱為垃圾收集器。
本文題目:Rust有GC,并且速度很快
鏈接分享:http://fisionsoft.com.cn/article/dposiie.html


咨詢
建站咨詢
