自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Rust有GC,并且速度很快

開發(fā) 后端
Rust越來越受歡迎。因此,不管Rust是否對我們都具有戰(zhàn)略意義,包括我自己在內(nèi)的一組同事對其進(jìn)行了為期半天的評估,以建立我們自己的觀點。我們按照標(biāo)準(zhǔn)入門書進(jìn)行了一些編碼,查看了一些框架,并觀看了“ Considering Rust”演示文稿。。

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

 

[[351165]]

垃圾收集簡史

當(dāng)您查看Rust的網(wǎng)站并閱讀介紹時,您會突然發(fā)現(xiàn)一個驕傲的聲明,Rust沒有垃圾收集器。如果您與我同齡,這會引起一些不好的回憶。有時候,您必須使用手動分配內(nèi)存,malloc()然后稍后再釋放它。如果過早釋放它,則會遇到諸如無效的內(nèi)存訪問異常之類的攻擊。如果忘記釋放它,則會造成內(nèi)存泄漏,從而使應(yīng)用程序受阻。很少有人在第一次就做對。這根本沒什么好玩的。

在研究Rust采取的方法之前,讓我們簡短地看看垃圾的實際含義。在Wikipedia中,有一個很好的定義:垃圾包括數(shù)據(jù)………在其上運行的程序在以后的任何計算中都不會使用。這意味著只有開發(fā)人員才能決定是否可以釋放存儲某些數(shù)據(jù)的內(nèi)存段。但是,應(yīng)用程序的運行時可以自動檢測垃圾的子集。如果在某個時間點不再存在對內(nèi)存段的引用,則程序?qū)o法訪問該段。并且,因此可以安全地刪除它。

為了實際實現(xiàn)這種支持,運行時必須分析應(yīng)用程序中的所有活動引用,并且必須檢查所有已分配的內(nèi)存引用(如果可以針對當(dāng)前應(yīng)用程序狀態(tài)訪問它們)。這是一項計算量很大的任務(wù)。在Java誕生的第一天,JVM突然凍結(jié),不得不在相當(dāng)長的時間內(nèi)進(jìn)行垃圾回收。如今,有很多用于垃圾收集的復(fù)雜算法,它們通常與應(yīng)用程序同時運行。但是,計算復(fù)雜度仍然相同。

從好的方面來說,應(yīng)用程序開發(fā)人員無需考慮手動釋放內(nèi)存段。永遠(yuǎn)不會有無效的內(nèi)存訪問異常。她仍然可以通過引用數(shù)據(jù)來創(chuàng)建內(nèi)存泄漏,現(xiàn)在不再需要。(恕我直言,主要的示例是自寫的緩存實現(xiàn)。老人的建議:切勿使用ehcache之類的方法。)但是,隨著垃圾收集器的引入,內(nèi)存泄漏的情況越來越少了。

Rust如何處理內(nèi)存段

乍一看,Rust看起來很像C,尤其是其引用和取消引用。但是它具有處理內(nèi)存的獨特方法。每個內(nèi)存段均由一個引用擁有。從開發(fā)人員的角度來看,始終只有一個變量擁有數(shù)據(jù)。如果此變量超出范圍且不再可訪問,則將所有權(quán)轉(zhuǎn)移到其他變量或釋放內(nèi)存。

使用這種方法,不再需要計算所有數(shù)據(jù)的可達(dá)性。取而代之的是,每次關(guān)閉命名上下文時(例如,通過從函數(shù)調(diào)用返回),都會使用簡單的算法來驗證所用內(nèi)存的可訪問性。聽起來好極了,以至于每個有經(jīng)驗的開發(fā)人員都可能立即想到一個問題:問題在哪里?

問題在于,開發(fā)人員必須照顧所有權(quán)。開發(fā)人員不必在整個應(yīng)用程序中漫不經(jīng)心地散布對數(shù)據(jù)的引用,而必須標(biāo)記所有權(quán)。如果所有權(quán)沒有明確定義,則編譯器將打印錯誤并停止工作。

為了進(jìn)行評估,與傳統(tǒng)的垃圾收集器相比,該方法是否真的有用,我看到兩個問題:

  • 開發(fā)人員在開發(fā)時標(biāo)記所有權(quán)有多難?如果她所有的精力都集中在與編譯器進(jìn)行斗爭而不是解決域問題上,那么這種方法所帶來的好處遠(yuǎn)不止于幫助。
  • 與傳統(tǒng)的垃圾收集器相比,Rust解決方案的速度快多少?如果收益不大,那為什么還要打擾呢?

為了回答這兩個問題,我在Rust和Kotlin中執(zhí)行了一項任務(wù)。該任務(wù)對于企業(yè)環(huán)境而言是典型的,會產(chǎn)生大量垃圾。第一個問題是根據(jù)我的個人經(jīng)驗和觀點回答的,第二個是通過具體測量得出的。

任務(wù):處理數(shù)據(jù)庫

我選擇的任務(wù)是模擬典型的以數(shù)據(jù)庫為中心的任務(wù),計算所有員工的平均收入。每個員工都被加載到內(nèi)存中,并且平均值會循環(huán)計算。我知道您絕對不應(yīng)在現(xiàn)實生活中這樣做,因為數(shù)據(jù)庫可以自己更快地完成此任務(wù)。但是,首先,我看到這種情況在現(xiàn)實生活中經(jīng)常發(fā)生,其次,對于某些NoSQL數(shù)據(jù)庫,您必須在應(yīng)用程序中執(zhí)行此操作,其次,這只是一些代碼,用于創(chuàng)建大量需要收集的垃圾。

我選擇了JVM上的Kotlin作為基于垃圾收集的編程語言的代表。JVM具有高度優(yōu)化的垃圾收集器,如果您習(xí)慣Kotlin,則使用Java就像在石器時代工作一樣。

您可以在GitHub上找到代碼:https://github.com/akquinet/GcRustVsJvm

用Kotlin處理

計算得出一系列員工,總結(jié)他們的薪水,計算員工數(shù)量,最后除以這些數(shù)字:

 

  1. fun computeAverageIncomeOfAllEmployees( 
  2.       employees : Sequence<Employee> 
  3.     ) : Double  
  4.    val (nrOfEmployees, sumOfSalaries) = employees 
  5.        .fold(Pair(0L, 0L), 
  6.          { (counter, sum), employee -> 
  7.               Pair(counter + 1, sum + employee.salary) 
  8.          }) 
  9.    return sumOfSalaries.toDouble() /  
  10.           nrOfEmployees.toDouble() 
  11.  } 

這里沒什么令人興奮的。(您可能會注意到一種函數(shù)式編程風(fēng)格。這是因為我非常喜歡函數(shù)式編程。但這不是本文的主題。)垃圾是在創(chuàng)建雇員時創(chuàng)建的。我在這里創(chuàng)建隨機(jī)雇員,以避免使用真實的數(shù)據(jù)庫。但是,如果您使用JPA,則將具有相同數(shù)量的對象創(chuàng)建。

 

  1. fun lookupAllEmployees( 
  2.         numberOfAllEmployees : Long 
  3.      ): Sequence<Employee> 
  4.    return (1L..numberOfAllEmployees) 
  5.             .asSequence() 
  6.             .map { createRandomEmployee() } 

隨機(jī)對象的創(chuàng)建也非常簡單。字符串是從字符列表創(chuàng)建的charPool。

 

  1. fun createRandomEmployee(): Employee = 
  2.    Employee( 
  3.      createRandomStringOf80Chars(), 
  4.      createRandomStringOf80Chars(), 
  5.      ... // code cut Out 
  6.    ) 
  7. fun createRandomStringOf80Chars() = 
  8.    (1..80) 
  9.       .map { nextInt(0, charPool.size) } 
  10.       .map(charPool::get) 
  11.       .joinToString(""

Rust版本的一個小驚喜是我必須如何處理前面提到的字符列表。因為只需要一個單例,所以將其存儲在一個伴隨對象中。這里是它的輪廓:

 

  1. class EmployeeServices { 
  2.   
  3.     companion object { 
  4.         private val charPool: List<Char>  
  5.            = ('a'..'z') + ('A'..'Z') + ('0'..'9'
  6.   
  7.         fun lookupAllEmployees(...) ... 
  8.         fun createRandomEmployee(): Employee ... 
  9.         fun computeAverageIncomeOfAllEmployees(...) ... 
  10.     } 

現(xiàn)在,以Rust方式處理

我偶然發(fā)現(xiàn)的第一件事是,將這個單例字符列表放在何處。Rust支持直接嵌入二進(jìn)制文件中的靜態(tài)數(shù)據(jù)和可以由編譯器內(nèi)聯(lián)的常量數(shù)據(jù)。兩種選擇僅支持一小部分表達(dá)式來計算單例的值。我計算允許的字符池的解決方案是這樣的:

  1. let char_pool = ('a'..'z').collect::>(); 

由于向量的計算基于類型推斷,因此無法將其指定為常量或靜態(tài)。我目前的理解是,Rust的慣用方法是添加功能需要處理的所有對象作為參數(shù)。因此,用于計算Rust中平均工資的主要調(diào)用如下所示:

 

  1. let average =  
  2.   compute_average_income_of_all_employees( 
  3.     lookup_all_employees( 
  4.             nr_of_employees, &char_pool, 
  5.   ) ); 

通過這種方法,所有依賴項都變得清晰了。具有C經(jīng)驗的開發(fā)人員會立即認(rèn)識到地址運算符&,該運算符將內(nèi)存地址作為指針返回,并且是高效且可能無法維護(hù)的代碼的基礎(chǔ)。當(dāng)我的許多同事與Rust一起玩時,這種基于C的負(fù)面體驗被投射到Rust。

我認(rèn)為這是不公平的。C的&運算符設(shè)計帶來的問題是,始終存在不可預(yù)測的副作用,因為應(yīng)用程序的每個部分都可以存儲指向存儲塊的指針。另外,每個部分都可以釋放內(nèi)存,從而可能導(dǎo)致所有其他部分引發(fā)異常。

在Rust中,&操作員的工作方式有所不同。每個數(shù)據(jù)始終由一個變量擁有。如果使用&此所有權(quán)創(chuàng)建了對數(shù)據(jù)的引用,則該所有權(quán)將轉(zhuǎn)移到引用范圍內(nèi)。只有所有者可以訪問數(shù)據(jù)。如果所有者超出范圍,則可以釋放數(shù)據(jù)。

在我們的示例中,char_pool使用&運算符將的所有權(quán)轉(zhuǎn)移到函數(shù)的參數(shù)。當(dāng)該函數(shù)返回時,所有權(quán)將歸還給變量char_pool。因此,它是一種類似于C的地址運算符,但它增加了所有權(quán)的概念,從而使代碼更簡潔。

Rust中的域邏輯

Rust的主要功能看起來與Kotlin差不多。由于隱含的數(shù)字類型,例如f6464位浮點數(shù),因此感覺有點基本。但是,這是您很快就會習(xí)慣的事情。

 

  1. fn compute_average_income_of_all_employees( 
  2.      employees: impl Iterator<Item=Employee> 
  3.    )  -> f64 
  4.     let (num_of_employees, sum_of_salaries) = 
  5.         employees.fold( 
  6.           (0u64, 0u64), 
  7.           |(counter, sum), employee| { 
  8.               return (counter + 1,  
  9.                       sum + employee.salary); 
  10.           }); 
  11.     return (sum_of_salaries as f64) /  
  12.            (num_of_employees as f64); 

恕我直言,這是一個很好的例子,可以證明Rust是一種非?,F(xiàn)代的干凈編程語言,并且對函數(shù)式編程風(fēng)格提供了很好的支持。

在Rust中創(chuàng)建垃圾

現(xiàn)在讓我們看一下程序的一部分,其中創(chuàng)建了許多對象,以后需要收集這些對象:

 

  1. fn lookup_all_employees<'a>( 
  2.      number_of_all_employees: u64, 
  3.      char_pool: &'a Vec<char
  4.    ) -> impl Iterator<Item=Employee> + 'a  
  5.     return 
  6.      (0..number_of_all_employees) 
  7.        .map(move | _ | { 
  8.          return create_random_employee(char_pool);  
  9.         }) 
  10.        .into_iter(); 

乍一看,這看起來很像Kotlin。它使用相同的功能樣式在循環(huán)中創(chuàng)建隨機(jī)雇員。返回類型是Iterator,類似于Kotlin中的序列,它是一個延遲計算的列表。

從第二個角度看,這些類型看起來很奇怪。這到底是'a什么?解決了懶惰評估的問題。由于Rust編譯器無法知道何時實際評估返回值,并且返回值取決于借入的引用,因此現(xiàn)在存在確定何時char_pool可以釋放借入值的問題。的'a注釋指定的壽命char_pool必須至少只要是作為返回值的壽命。

對于習(xí)慣了經(jīng)典垃圾回收的開發(fā)人員來說,這是一個新概念。在Rust中,她有時必須明確指定對象的生存期。垃圾收集器進(jìn)行所有清理時不需要的東西。

第三,您可以發(fā)現(xiàn)move關(guān)鍵字。它強制閉包獲取其使用的所有變量的所有權(quán)。由于char_pool(再次),這是必要的。Map是延遲執(zhí)行的,因此,從編譯器的角度來看,閉包可能會超出變量的壽命char_pool。因此,關(guān)閉必須擁有它的所有權(quán)。

其余代碼非常簡單。這些結(jié)構(gòu)是根據(jù)隨機(jī)創(chuàng)建的字符串創(chuàng)建的:

 

  1. fn create_random_employee( 
  2.      char_pool: &Vec<char
  3.    ) -> Employee  
  4.   return Employee { 
  5.     first_name:  
  6.       create_random_string_of_80_chars(char_pool), 
  7.     last_name:  
  8.       create_random_string_of_80_chars(char_pool), 
  9.     address: Address 
  10.     { // cut out .. }, 
  11.         salary: 1000, 
  12.     }; 
  13.   
  14. fn create_random_string_of_80_chars( 
  15.      char_pool: &Vec<char
  16.    ) -> String  
  17.     return (0..80) 
  18.         .map(|_| {  
  19.           char_pool[ 
  20.             rand::thread_rng() 
  21.                .gen_range(0, 
  22.                           char_pool.len())] 
  23.          }) 
  24.         .into_iter().collect(); 

那么,Rust有多難?

實施這個微小的測試程序非常復(fù)雜。Rust是一種現(xiàn)代的編程語言,使開發(fā)人員能夠快速干凈地維護(hù)代碼。但是,它的內(nèi)存管理概念直接體現(xiàn)在語言的所有元素中,這是開發(fā)人員必須理解的。

工具支持很好,恕我直言。大多數(shù)時候,您只需要執(zhí)行編譯器告訴您的操作即可。但是有時您必須實際決定要如何處理數(shù)據(jù)。

現(xiàn)在,值得嗎?

即使聽起來有些令人信服,但我還是非常樂于做一些測量,看看現(xiàn)實是否也令人信服。因此,我為四個不同的輸入大小運行了Rust和Kotlin應(yīng)用程序,測量了時間,并將結(jié)果放在對數(shù)比例圖中:

 

Rust有GC,并且速度很快

看著這些數(shù)字,我長了很長的臉。銹總是較慢;對于10 ^ 6個元素,一個非常糟糕的因子是11。這不可能。我檢查了代碼,沒有發(fā)現(xiàn)錯誤。然后,我檢查了優(yōu)化情況,并發(fā)現(xiàn)了--release從dev模式切換到的標(biāo)志prod?,F(xiàn)在,結(jié)果看起來好多了:

 

Rust有GC,并且速度很快

這樣好多了。現(xiàn)在,Rust總是比Kotlin快,并提供線性性能。在Kotlin上,我們看到了較長時間運行的代碼的典型性能改進(jìn),這可能是由于及時編譯引起的。從10 ^ 4的輸入大小來看,Rust大約比Kotlin快3倍??紤]到JVM的成熟以及過去幾十年在基礎(chǔ)架構(gòu)上投入的資源,這是非常令人印象深刻的(Java的第一版于1995年發(fā)布)。

對于我來說,令人驚訝的是與生產(chǎn)配置文件相比,開發(fā)配置文件的速度要慢得多。40的系數(shù)是如此之大,以至于您永遠(yuǎn)都不應(yīng)將開發(fā)配置文件用于發(fā)行。

結(jié)論

Rust是一種現(xiàn)代編程語言,具有您如今已習(xí)慣的所有舒適性。它具有一種新的內(nèi)存處理方法,這給開發(fā)人員帶來了一點額外負(fù)擔(dān),同時還提供了出色的性能。

而且,要回答標(biāo)題的最初問題,您不必手動處理Rust中的垃圾。此垃圾收集由運行時系統(tǒng)完成,但現(xiàn)在不再稱為垃圾收集器。

責(zé)任編輯:未麗燕 來源: 今日頭條
相關(guān)推薦

2024-01-15 11:56:55

lintersESLint

2024-03-11 15:47:11

RustPython代碼

2016-03-02 17:55:03

app用戶加載

2024-11-04 14:13:19

2021-08-07 09:35:18

Starlink網(wǎng)速寬帶

2019-01-02 09:10:13

RustGraydon Hoa社區(qū)

2023-05-04 07:34:37

Rust代碼CPU

2024-09-09 16:25:09

2023-12-07 12:21:04

GCJVM垃圾

2023-02-20 08:00:02

Rust語言系統(tǒng)

2024-01-09 16:14:39

RustGo切片

2017-09-12 13:04:00

AI人工智能機(jī)器人

2024-04-01 11:43:51

Rust開發(fā)插件

2021-06-18 15:15:51

機(jī)器學(xué)習(xí)Rust框架

2024-01-09 15:51:56

Rust開發(fā)Trait

2024-04-03 10:00:44

Rust編譯開發(fā)

2020-07-15 08:00:52

Rust語言技巧

2021-11-25 16:25:53

代碼開發(fā)技術(shù)

2024-06-20 12:48:17

Rustfd

2024-01-10 09:18:58

RustAIGPT
點贊
收藏

51CTO技術(shù)棧公眾號