對(duì)比了 Python弃甥、Go 和 Rust 之后,我們得出了這個(gè)結(jié)論

本文主要是從 Python 開(kāi)發(fā)者的角度基于開(kāi)發(fā)工效學(xué)對(duì)這三種語(yǔ)言進(jìn)行的一個(gè)比較汁讼,你可以跳過(guò)討論部分淆攻,直接查看代碼示例、性能比較嘿架、主要結(jié)論或 Python瓶珊、Go 和 Rust 的 diffimg 實(shí)現(xiàn)。

本文主要是從 Python 開(kāi)發(fā)者的角度基于開(kāi)發(fā)工效學(xué)對(duì)這三種語(yǔ)言進(jìn)行的一個(gè)比較眶明,你可以跳過(guò)討論部分艰毒,直接查看代碼示例性能比較(如果你想要一些硬數(shù)據(jù))搜囱、主要結(jié)論?Python?丑瞧、?Go??Rust?的 diffimg 實(shí)現(xiàn)柑土。

幾年前,我負(fù)責(zé)重寫一個(gè)圖像處理服務(wù)绊汹。為了弄清楚對(duì)于給定的圖像和一個(gè)或多個(gè)轉(zhuǎn)換(調(diào)整大小稽屏、圓形裁剪、修改格式等)西乖,我的新服務(wù)創(chuàng)建的輸出是否和舊服務(wù)一致狐榔,我必須自己檢查圖像。顯然获雕,我需要自動(dòng)化薄腻,但我找不到一個(gè)現(xiàn)有的 Python 庫(kù)可以告訴我,這兩張圖片在像素級(jí)上有什么不同届案,因此有了diffimg?庵楷,它可以給你一個(gè)差異比 / 百分比,或生成差異圖像(檢出 readme楣颠,里面有一個(gè)例子)尽纽。

我最初是用 Python 實(shí)現(xiàn)的(我最熟悉的語(yǔ)言),主要部分使用了?Pillow?童漩。它可以用作庫(kù)或命令行工具弄贿。程序的基本部分非常小,只有幾十行矫膨,這要感謝Pillow差凹。構(gòu)建這個(gè)工具并不費(fèi)力(?xkcd 是對(duì)的,幾乎所有東西都有一個(gè) Python 模塊)豆拨,但它至少對(duì)于除我自己之外的幾十人是有用的直奋。

幾個(gè)月前能庆,我加入了一家公司施禾,他們有幾個(gè)服務(wù)是用 Go 編寫的,我需要快速上手這門語(yǔ)言搁胆。編寫diffimg-go?看起來(lái)很有趣弥搞,甚至可能是一種有用的方法。這里有一些來(lái)自經(jīng)驗(yàn)的興趣點(diǎn)渠旁,以及一些在工作中使用它的興趣點(diǎn)攀例。

對(duì)比 Python 和 Go

(代碼:?diffimg?(Python)和?diffimg-go?

標(biāo)準(zhǔn)庫(kù):Go 有一個(gè)相當(dāng)不錯(cuò)的?image?標(biāo)準(zhǔn)庫(kù)模塊,以及命令行?flag?解析庫(kù)顾腊。我不需要尋找任何外部依賴粤铭;diffimg-go 實(shí)現(xiàn)沒(méi)有依賴,而 Python 實(shí)現(xiàn)使用了相當(dāng)重量級(jí)的第三方模塊(諷刺的是)Pillow杂靶。Go 的標(biāo)準(zhǔn)庫(kù)更有條理梆惯,而且經(jīng)過(guò)深思熟慮酱鸭,而 Python 的會(huì)逐步發(fā)展,它們是多年來(lái)由許多作者創(chuàng)建的垛吗,有許多不同的約定凹髓。Go 標(biāo)準(zhǔn)庫(kù)的一致性使開(kāi)發(fā)者更易于預(yù)測(cè)任何給定的模塊將如何發(fā)揮作用,而且源代碼有非常好的文檔記錄怯屉。

使用標(biāo)準(zhǔn) image 庫(kù)的一個(gè)缺點(diǎn)是它不自動(dòng)檢測(cè)圖像是否有一個(gè) alpha 通道蔚舀;所有圖像類型的像素值都有四個(gè)通道(RGBA)。因此锨络,diffimg-go 實(shí)現(xiàn)要求用戶指明是否要使用 alpha 通道赌躺。這個(gè)小小的不便不值得找第三方庫(kù)來(lái)修復(fù)。

一個(gè)很大的好處是羡儿,標(biāo)準(zhǔn)庫(kù)中有足夠的內(nèi)容寿谴,你不需要像 Django 這樣的 Web 框架。用 Go 有可能在沒(méi)有任何依賴關(guān)系的情況下建立一個(gè)真正可用的 Web 服務(wù)失受。Python 號(hào)稱“自帶電池(batteries-included)”讶泰,但在我看來(lái),Go 做得更好拂到。

靜態(tài)類型系統(tǒng):我過(guò)去使用靜態(tài)類型語(yǔ)言痪署,但我過(guò)去幾年一直在使用 Python 編程。體驗(yàn)起初有點(diǎn)煩人兄旬,感覺(jué)它好像只是減慢了我的速度狼犯,它迫使我過(guò)度明確,即使我偶爾錯(cuò)了领铐,Python 也會(huì)讓我做我想做的悯森。有點(diǎn)像你給人發(fā)指令而對(duì)方總是打斷你讓你闡明你是什么意思,而有的人則總是點(diǎn)頭绪撵,看上去已經(jīng)理解你瓢姻,但是你并不確定他們是否已經(jīng)全部了解。它將大大減少與類型相關(guān)的 Bug音诈,但是我發(fā)現(xiàn)幻碱,我仍然需要花幾乎相同的時(shí)間編寫測(cè)試。

Go 的一個(gè)常見(jiàn)缺點(diǎn)是它沒(méi)有用戶可實(shí)現(xiàn)的泛型類型细溅。雖然這不是一個(gè)構(gòu)建大型可擴(kuò)展應(yīng)用程序的必備特性褥傍,但它肯定會(huì)減緩開(kāi)發(fā)速度。雖然已經(jīng)有替代模式建議喇聊,但是它們中沒(méi)有一個(gè)和真正的泛型類型一樣有效恍风。

靜態(tài)類型系統(tǒng)的一個(gè)優(yōu)點(diǎn)是,可以更簡(jiǎn)單快速地閱讀不熟悉的代碼庫(kù)。用好類型可以帶來(lái)許多動(dòng)態(tài)類型系統(tǒng)中會(huì)丟失的額外信息朋贬。

接口和結(jié)構(gòu):Go 使用接口和結(jié)構(gòu)鸥咖,而 Python 使用類。在我看來(lái)兄世,這可能是最有趣的區(qū)別啼辣,因?yàn)樗仁刮覅^(qū)分定義行為的類型和保存信息的類型這兩個(gè)概念。Python 和其他傳統(tǒng)的面向?qū)ο蟮恼Z(yǔ)言都鼓勵(lì)你將它們混在一起御滩,但這兩種范式各有利弊:

Go 強(qiáng)烈建議組合而不是繼承鸥拧。雖然它通過(guò)嵌入繼承,不用類削解,但其數(shù)據(jù)和方法不是那么容易傳遞富弦。通常,我認(rèn)為組合是更好的默認(rèn)模式氛驮,但我不是一個(gè)絕對(duì)主義者腕柜,在某些情況下繼承更合適,所以我不喜歡語(yǔ)言幫我作出這個(gè)決定矫废。

接口實(shí)現(xiàn)的分離意味著盏缤,如果有許多類型彼此相似,你就需要多次編寫類似的代碼蓖扑。由于缺少泛型類型唉铜,在 Go 中,有些情況下我無(wú)法重用代碼律杠,不過(guò)在 Python 中可以潭流。

然而,由于 Go 是靜態(tài)類型的柜去,當(dāng)你編寫的代碼會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤時(shí)灰嫉,編譯器 / 源碼分析器(linter)會(huì)告訴你。Python 源碼分析器也可以有一點(diǎn)這樣的功能嗓奢。但在 Python 中讼撒,當(dāng)你試圖訪問(wèn)一個(gè)可能不存在的方法或?qū)傩詴r(shí),由于語(yǔ)言的動(dòng)態(tài)性蔓罚,Python 源碼分析器無(wú)法確切地知道什么方法 / 屬性存在蜓竹,直到運(yùn)行時(shí)间影。靜態(tài)定義的接口和結(jié)構(gòu)是唯一在編譯時(shí)和開(kāi)發(fā)過(guò)程中知道什么可用的方法熏瞄,這使得編譯時(shí)報(bào)錯(cuò)的 Go 比運(yùn)行時(shí)報(bào)錯(cuò)的 Python 更可靠错英。

沒(méi)有可選參數(shù):Go 只有可變函數(shù)岩调,類似于 Python 的關(guān)鍵字參數(shù)戈泼,但不那么有用候址,因?yàn)閰?shù)需要是相同的類型慢叨。我發(fā)現(xiàn)關(guān)鍵字參數(shù)是我真正懷念的特性,這主要是你可以把任何類型的一個(gè) kwarg 扔給任何需要它的函數(shù)丽惭,而無(wú)需重寫它的每一個(gè)調(diào)用击奶,這讓重構(gòu)簡(jiǎn)單了許多。我在工作中經(jīng)常使用這個(gè)特性责掏,它為我節(jié)省了很多時(shí)間柜砾。由于沒(méi)有該特性,這使得我在處理是否應(yīng)該基于命令行標(biāo)志創(chuàng)建差異圖像時(shí)顯得有些笨拙换衬。

冗長(zhǎng):Go 有點(diǎn)冗長(zhǎng)(盡管不是像 Java 那么冗長(zhǎng))痰驱。這部分是因?yàn)槠漕愋拖到y(tǒng)沒(méi)有泛型,但主要是因?yàn)檎Z(yǔ)言本身很小瞳浦,沒(méi)有提供很多特性(你只有一種循環(huán)結(jié)構(gòu)可以使用5S场)。我懷念Python 的列表推導(dǎo)式(list comprehensions)和其他函數(shù)式編程特性叫潦。如果你熟悉Python蝇完,你一兩天就可以學(xué)完?Tour of Go?,然后你就了解了整個(gè)語(yǔ)言矗蕊。

錯(cuò)誤處理:Python 有異常短蜕,而 Go 在可能出錯(cuò)的地方通過(guò)從函數(shù)返回元組 value, error 來(lái)傳播錯(cuò)誤傻咖。Python 允許你在調(diào)用棧中的任何位置捕獲錯(cuò)誤忿危,而不需要你一次又一次地手動(dòng)將錯(cuò)誤傳遞回去。這又使得代碼簡(jiǎn)潔没龙,而不像 Go 那樣被其臭名昭著的 if err != nil 模式搞得雜亂無(wú)章铺厨,不過(guò)你需要弄清楚函數(shù)及其內(nèi)部的所有調(diào)用可能拋出的異常(使用 except Exception: 通常是一種糟糕的變通方案)。良好的文檔注釋和測(cè)試可以提供幫助硬纤,你在使用其中任何一種語(yǔ)言編程時(shí)都應(yīng)該添加解滓。Go 的系統(tǒng)絕對(duì)更安全。如果你忽視了 err 值筝家,你還是可以搬起石頭砸自己的腳洼裤,但該系統(tǒng)使糟糕的主意變得更明顯。

第三方模塊:在?Go 模塊出現(xiàn)之前溪王,Go 的包管理器會(huì)把所有下載的包扔到?GOPATH/src目錄下腮鞍,而不是項(xiàng)目的目錄(像大多數(shù)其他語(yǔ)言)。GOPATH/src目錄下莹菱,而不是項(xiàng)目的目錄(像大多數(shù)其他語(yǔ)言)移国。GOPATH 下這些模塊的路徑也會(huì)從托管包的 URL 構(gòu)建,所以你的 import 語(yǔ)句將是類似 import "github.com/someuser/somepackage?" 這個(gè)樣子道伟。在幾乎所有 Go 代碼庫(kù)的源代碼中嵌入 github.com 似乎是一個(gè)奇怪的選擇迹缀。在任何情況下使碾,Go 允許以傳統(tǒng)的方式做事,但 Go 模塊仍然是個(gè)很新的特性祝懂,所以在一段時(shí)間內(nèi)票摇,在缺少管理的 Go 代碼庫(kù)中,這種奇怪的行為仍將很常見(jiàn)砚蓬。

異步性:Goroutines 是啟動(dòng)異步任務(wù)的一種非常方便的方法矢门。在 async/await 之前,Python 的異步解決方案有點(diǎn)令人沮喪灰蛙。遺憾的是颅和,我沒(méi)有用 Python 或 Go 編寫很多真實(shí)的異步代碼,而 diffimg 的簡(jiǎn)單性似乎并不適合說(shuō)明異步性的額外開(kāi)銷缕允,所以我沒(méi)有太多要說(shuō)的峡扩,雖然我確實(shí)喜歡使用 Go 的channels?來(lái)處理多個(gè)異步任務(wù)。我的理解是障本,對(duì)于性能教届,Go 仍然占了上風(fēng)了,因?yàn)?goroutine 可以充分利用多處理器并發(fā)驾霜,而 Python 的基本 async/await 仍局限于一個(gè)處理器案训,所以主要用于 I / O 密集型任務(wù)。

調(diào)試:Python 勝出粪糙。pdb(以及像?ipdb?這樣更復(fù)雜的選項(xiàng))非常靈活强霎,一旦你進(jìn)入 REPL,你可以編寫任何你想要的代碼蓉冈。?Delve?是一個(gè)很好的調(diào)試器城舞,但和直接放入解釋器不一樣,語(yǔ)言的全部功能都唾手可得寞酿。

Go 總結(jié)

我對(duì) Go 最初的印象是家夺,由于它的抽象能力(有意為之)有限,所以它不像 Python 那樣有趣伐弹。Python 有更多的特性拉馋,因此有更多的方法來(lái)做一些事情,找到最快惨好、最易讀或“最聰明”的解決方案可能會(huì)很有趣煌茴。Go 會(huì)積極地阻止你變得“聰明”。我認(rèn)為日川,Go 的優(yōu)勢(shì)在于它并不聰明蔓腐。

它的極簡(jiǎn)主義和缺乏自由限制了單個(gè)開(kāi)發(fā)人員實(shí)現(xiàn)一個(gè)想法。然而逗鸣,當(dāng)項(xiàng)目擴(kuò)展到幾十個(gè)或幾百個(gè)開(kāi)發(fā)人員時(shí)合住,這個(gè)弱點(diǎn)變成了它的力量——因?yàn)槊總€(gè)人都使用同樣的語(yǔ)言特性小工具集绰精,更容易統(tǒng)一撒璧,因此可以被他人理解透葛。使用 Go 仍有可能編寫糟糕的代碼,但與那些更“強(qiáng)大”的語(yǔ)言相比卿樱,它讓你更難創(chuàng)造怪物僚害。

使用一段時(shí)間后,我理解了為什么像谷歌這樣的公司想要一門這樣的語(yǔ)言繁调。新工程師不斷地加入到大量代碼庫(kù)的開(kāi)發(fā)萨蚕,在更復(fù)雜 / 更強(qiáng)大的語(yǔ)言中,在最后期限的壓力下蹄胰,引入復(fù)雜性的速度比它被消除的速度更快岳遥。防止這種情況發(fā)生的最好方法是使用一種更不容易產(chǎn)生復(fù)雜性的語(yǔ)言。

所以說(shuō)裕寨,我很高興工作在一個(gè)大型應(yīng)用程序上下文中的 Go 代碼庫(kù)上浩蓉,有一個(gè)多樣化和不斷增長(zhǎng)的團(tuán)隊(duì)。事實(shí)上宾袜,我認(rèn)為我更喜歡它捻艳。我只是不想把它用于我自己的個(gè)人項(xiàng)目。

進(jìn)入 Rust

幾周前庆猫,我決定嘗試學(xué)習(xí) Rust认轨。我之前曾試圖這樣做,但發(fā)現(xiàn)類型系統(tǒng)和借用檢查(borrow checker)讓人困惑月培,沒(méi)有足夠的背景信息嘁字,我不知道為什么要把所有這些限制強(qiáng)加給我,對(duì)于我想做的任務(wù)來(lái)說(shuō)大而笨重杉畜。然而拳锚,自那時(shí)以來(lái),我對(duì)程序執(zhí)行時(shí)內(nèi)存中發(fā)生了什么有了更多的了解寻行。我從這本書開(kāi)始霍掺,而不是試圖一頭扎進(jìn)去。這有很大的幫助拌蜘,這份介紹比我見(jiàn)過(guò)的任何編程語(yǔ)言的介紹都要好杆烁。

在我讀過(guò)這本書的前十幾章之后,我覺(jué)得自己有足夠的信心嘗試 diffimg 的另一個(gè)實(shí)現(xiàn)(這時(shí)简卧,我覺(jué)得我的 Rust 經(jīng)驗(yàn)與我寫 diffimg-go 時(shí)的 Go 經(jīng)驗(yàn)一樣多)兔魂。這比我用 Go 實(shí)現(xiàn)的時(shí)間要長(zhǎng),而后者比用 Python 需要的時(shí)間更長(zhǎng)举娩。我認(rèn)為析校,即使考慮到我對(duì) Python 更加熟悉构罗,這也是對(duì)的——兩種語(yǔ)言都有更多的東西要寫。

在編寫?diffimg-rs?時(shí)智玻,我注意到一些事情遂唧。

類型系統(tǒng):現(xiàn)在,我已經(jīng)習(xí)慣 Go 中更基本的靜態(tài)類型系統(tǒng)了吊奢,但 Rust 更強(qiáng)大(也更復(fù)雜)盖彭。有了 Go 中接口和結(jié)構(gòu)的基礎(chǔ)(它們都簡(jiǎn)單得多),泛型類型页滚、枚舉類型召边、traits、引用類型裹驰、生命周期就是全部我要額外學(xué)習(xí)的概念了隧熙。此外,Rust 使用其類型系統(tǒng)實(shí)現(xiàn)其它語(yǔ)言不使用類型系統(tǒng)實(shí)現(xiàn)的特性(如?Result?幻林,我很快會(huì)介紹)贞盯。幸運(yùn)的是,編譯器 / 源碼解析器非常有幫助滋将,它可以告訴你你做錯(cuò)了什么邻悬,甚至經(jīng)常告訴你如何解決這個(gè)問(wèn)題。盡管如此随闽,這比我學(xué)習(xí) Go 的類型系統(tǒng)所用的時(shí)間要多得多父丰,我還沒(méi)有適應(yīng)它的所有特性。

有一個(gè)地方掘宪,因?yàn)轭愋拖到y(tǒng)蛾扇,我正在使用的圖像庫(kù)的實(shí)現(xiàn)會(huì)導(dǎo)致令人不快的代碼重復(fù)量。我最終只要匹配兩個(gè)最重要的枚舉類型魏滚,但匹配其他類型會(huì)導(dǎo)致另外半打左右?guī)缀跸嗤拇a行镀首。在這個(gè)規(guī)模上,這不是一個(gè)問(wèn)題鼠次,但它使我生氣更哄。也許這里使用宏是個(gè)不錯(cuò)的主意,我仍然需要試驗(yàn)腥寇。

復(fù)制代碼

letmutdiff =matchimage1.color() {

? ? ? ? image::ColorType::RGB(_) => image::DynamicImage::new_rgb8(w成翩, h),

? ? ? ? image::ColorType::RGBA(_) => image::DynamicImage::new_rgba8(w赦役, h)麻敌,

// 繼續(xù)匹配所有 7 種類型?

_ =>returnErr(

format!("color mode {:?} not yet supported"掂摔, image1.color())

? ? ? ? )术羔,

? ? }赢赊;

手動(dòng)內(nèi)存管理:Python 和 Go 會(huì)幫你撿垃圾。C 允許你亂丟垃圾级历,但當(dāng)它踩到你丟的香蕉皮時(shí)释移,它會(huì)大發(fā)脾氣。Rust 會(huì)拍你一下鱼喉,并要求你自己清理干凈秀鞭。這會(huì)刺痛我趋观,甚至超過(guò)了從動(dòng)態(tài)類型語(yǔ)言遷移到靜態(tài)類型語(yǔ)言扛禽,因?yàn)槲冶粚檳牧耍ǔ6际怯烧Z(yǔ)言跟在我后面撿皱坛。此外编曼,編譯器會(huì)設(shè)法盡可能地幫助你,但你仍然需要大量的學(xué)習(xí)才能理解到底發(fā)生了什么剩辟。

直接訪問(wèn)內(nèi)存(以及 Rust 的函數(shù)式編程特性)的一個(gè)好處是掐场,它簡(jiǎn)化了差異比的計(jì)算,因?yàn)槲铱梢院?jiǎn)單地映射原始字節(jié)數(shù)組贩猎,而不必按坐標(biāo)索引每個(gè)像素熊户。

函數(shù)式特性:Rust 強(qiáng)烈鼓勵(lì)函數(shù)式的方法:它有 FP 友好的類型系統(tǒng)(類似 Haskell)、不可變類型吭服、閉包嚷堡,迭代器,模式匹配等艇棕,但也允許命令式代碼蝌戒。它類似于編寫 OCaml(有趣的是,最初的 Rust 編譯器是用 OCaml 編寫的)沼琉。因此北苟,對(duì)于這門與 C 競(jìng)爭(zhēng)的語(yǔ)言,代碼比你想象得更簡(jiǎn)潔打瘪。

錯(cuò)誤處理:不同于 Python 使用的異常模型友鼻,也不同于 Go 異常處理時(shí)返回的元組,Rust 利用枚舉類型:Result 返回 Ok(value) 或 Err(error)闺骚。這和 Go 的方式更接近彩扔,但更明確一些,而且利用了類型系統(tǒng)葛碧。還有一個(gè)語(yǔ)法糖用于檢查語(yǔ)句中的 Err 并提前返回:? 操作符(在我看來(lái)借杰,Go 可以用類似這樣的東西)。

異步性:Rust 的 async/await 還沒(méi)有完全準(zhǔn)備好进泼,但最終語(yǔ)法最近已經(jīng)達(dá)成一致蔗衡。Rust 標(biāo)準(zhǔn)庫(kù)中也有一些基本的線程特性纤虽,看上去比 Python 的更容易使用,但我沒(méi)有花太多的時(shí)間了解它绞惦。Go 似乎仍然有最好的特性逼纸。

工具:rustup 和 cargo 分別是非常優(yōu)秀的語(yǔ)言版本管理器和包 / 模塊管理器實(shí)現(xiàn)。一切“正臣貌酰”杰刽。我特別喜歡自動(dòng)生成的文檔。對(duì)于這些工具王滤,Python 提供的選項(xiàng)有點(diǎn)太簡(jiǎn)單贺嫂,需要小心對(duì)待,正如我之前提到的雁乡,Go 有一種奇怪的管理模塊方式第喳,但除此之外,它的工具比 Python 的更好踱稍。

編輯器插件:我的. vimrc 文件大到令人尷尬曲饱,至少有三十多個(gè)插件。我有一些用于代碼檢查珠月、自動(dòng)補(bǔ)全以及格式化 Python 和 Go 的插件扩淀,但相比于其他兩種語(yǔ)言,Rust 插件更容易設(shè)置啤挎,更有用驻谆、更一致。?rust.vim??vim-lsp?插件(以及?Rust 語(yǔ)言服務(wù)器)是我獲得一個(gè)非常強(qiáng)大的配置所需要的全部侵浸。我還沒(méi)有測(cè)試其他編輯器旺韭,但是,借助 Rust 提供的編輯器無(wú)關(guān)的優(yōu)秀工具掏觉,我認(rèn)為它們一樣有幫助区端。這個(gè)設(shè)置提供了我使用過(guò)的最好的“轉(zhuǎn)到定義”。它可以很好地適用于本地澳腹、標(biāo)準(zhǔn)庫(kù)和開(kāi)箱即用的第三方代碼织盼。

調(diào)試:我還沒(méi)有嘗試過(guò) Rust 的調(diào)試器(因?yàn)槠漕愋拖到y(tǒng)和 println! 已經(jīng)讓我走得很遠(yuǎn)),但是你可以使用 rust-gdb 和 rust-lldb酱塔,以及 rustup 初始安裝時(shí)帶有的 gdb 和 lldb 調(diào)試器的封裝器沥邻。如果你之前在編寫 C 語(yǔ)言代碼時(shí)使用過(guò)那些調(diào)試器,那么其使用體驗(yàn)將是意料之中的羊娃。正如前面提到的唐全,編譯器的錯(cuò)誤消息非常有幫助。

Rust 總結(jié)

你至少要讀過(guò)這本書的前幾章,否則我絕對(duì)不建議你嘗試編寫 Rust 代碼邮利,即使你已經(jīng)熟悉 C 和內(nèi)存管理弥雹。對(duì)于 Go 和 Python,只要你有一些另一種現(xiàn)代命令式編程語(yǔ)言的經(jīng)驗(yàn)延届,它們就不是很難入手剪勿,必要時(shí)可以參考文檔。Rust 是一門很大的語(yǔ)言方庭。Python 也有很多特性厕吉,但是它們大部分是可選的。只要理解一些基本的數(shù)據(jù)結(jié)構(gòu)和一些內(nèi)置函數(shù)械念,你就可以完成很多工作头朱。對(duì)于 Rust,你需要真正理解類型系統(tǒng)的固有復(fù)雜性和借用檢查订讼,否則你會(huì)搞得十分復(fù)雜髓窜。

就我寫 Rust 時(shí)的感覺(jué)而言扇苞,它非常有趣欺殿,就像 Python 一樣。它廣泛的特性使它很有表現(xiàn)力鳖敷。雖然編譯器會(huì)經(jīng)常讓你停下脖苏,但它也非常有用,而且它對(duì)于如何解決你的借用問(wèn)題 / 輸入問(wèn)題的建議通常是有效的定踱。正如我所提到的棍潘,這些工具是我遇到的所有語(yǔ)言中最好的,并且不像我使用的其他一些語(yǔ)言那樣給我?guī)?lái)很多麻煩崖媚。我非常喜歡使用這種語(yǔ)言亦歉,并將在 Python 的性能還不夠好的地方繼續(xù)尋找使用 Rust 的機(jī)會(huì)。

代碼示例

我提取了每個(gè) diffimg 中計(jì)算差異比的代碼塊畅哑。為了概括介紹 Python 的做法肴楷,這需要 Pillow 生成的差異圖像,對(duì)所有通道的所有像素值求和荠呐,并返回最大可能值(相同大小的純白圖像)除以總和的比值赛蔫。

Python

復(fù)制代碼

diff_img= ImageChops.difference(im1, im2)

stat= ImageStat.Stat(diff_img)

sum_channel_values= sum(stat.mean)

max_all_channels= len(stat.mean) *255

diff_ratio= sum_channel_values / max_all_channels

對(duì)于 Go 和 Rust泥张,方法有點(diǎn)不同:我們不用創(chuàng)建一個(gè)差異圖像呵恢,我們只要遍歷兩幅輸入圖像,并對(duì)每個(gè)像素的差異求和媚创。在 Go 中渗钉,我們用坐標(biāo)索引每幅圖像……

Go

復(fù)制代碼

funcGetRatio(im1, im2 image.Image钞钙, ignoreAlphabool)float64{

varsumuint64

? width鳄橘, height := getWidthAndHeight(im1)

fory :=0粤剧; y < height; y++ {

forx :=0挥唠; x < width抵恋; x++ {

sum +=uint64(sumPixelDiff(im1, im2宝磨, x弧关, y, ignoreAlpha))

? ? }

? }

varnumChannels =4

ifignoreAlpha {

numChannels =3

? }

? totalPixVals := (height * width) * (maxChannelVal * numChannels)

returnfloat64(sum) /float64(totalPixVals)

}

……但在 Rust 中唤锉,我們將圖像視為它們真的是在內(nèi)存中世囊,是一系列可以壓縮到一起并消費(fèi)的字節(jié)。

Rust

復(fù)制代碼

pubfncalculate_diff(

? ? image1: DynamicImage窿祥,

? ? image2: DynamicImage

) ->f64{

letmax_val =u64::pow(2株憾,8) -1;

letmutdiffsum:u64=0晒衩;

for(&p1嗤瞎, &p2)inimage1

? ? ? .raw_pixels()

? ? ? .iter()

? ? ? .zip(image2.raw_pixels().iter()) {

diffsum +=u64::from(abs_diff(p1, p2))听系;

? }

lettotal_possible = max_val * image1.raw_pixels().len()asu64贝奇;

letratio = diffsumasf64/ total_possibleasf64;

? ratio

}

對(duì)于這些例子靠胜,有一些事情需要注意:

Python 的代碼最少掉瞳。顯然,這在很大程度上取決于使用的圖像庫(kù)浪漠,但這說(shuō)明了使用 Python 的一般體驗(yàn)陕习。在許多情況下,那些庫(kù)為你做了很多工作址愿,因?yàn)樯鷳B(tài)系統(tǒng)非常發(fā)達(dá)该镣,任何東西都有成熟的解決方案。

在 Go 和 Rust 示例中有類型轉(zhuǎn)換必盖。每個(gè)代碼塊中都使用了三個(gè)數(shù)值類型:用于像素通道值的 uint8/u8(Go 和 Rust 有類型推斷拌牲,所以你看不到任何明確提及的類型)、用于總和的 uint64/u64 和用于最終比值的 float64/f64歌粥。對(duì)于 Go 和 Rust塌忽,需要花時(shí)間統(tǒng)一類型,而 Python 將隱式地完成這一切失驶。

Go 實(shí)現(xiàn)的風(fēng)格是命令式的土居,但也是明確和可以理解的(使其稍顯欠缺的是我前面提到的 ignoreAlpha),甚至是對(duì)那些不習(xí)慣該語(yǔ)言的人也是如此。Python 示例相當(dāng)清晰擦耀,一旦你理解了 ImageStat 在做什么棉圈。對(duì)于那些不熟悉這種語(yǔ)言的人來(lái)說(shuō),Rust 肯定更加難懂:

.raw_pixels() 獲取圖像眷蜓,生成 8 位無(wú)符號(hào)整數(shù)向量分瘾;

.iter() 為該向量創(chuàng)建一個(gè)迭代器。默認(rèn)情況下吁系,向量是不可遍歷的德召;

.zip() 你可能了解,它接受兩個(gè)迭代器汽纤,然后生成一個(gè)迭代器上岗,每個(gè)元素是一個(gè)元組:(來(lái)自第一個(gè)向量的元素,來(lái)自第二個(gè)向量的元素)蕴坪;

在 diffsum 聲明中肴掷,我們需要一個(gè) mut,因?yàn)樽兞磕J(rèn)是不可變的背传;

如果你熟悉 C呆瞻,你就會(huì)明白為什么 for (&p1, &p2) 中有 &:迭代器生成像素值的引用续室,但 abs_diff() 自己取得它們的值栋烤。Go 支持指針(和引用不太一樣),但它們不同于 Rust 中常用的引用挺狰。

函數(shù)中的最后一個(gè)語(yǔ)句用于在沒(méi)有行結(jié)束符 ; 的情況作為返回值。其他一些函數(shù)式語(yǔ)言也是這樣做的买窟。

這段是為了讓你了解需要掌握多少特定于語(yǔ)言的知識(shí)才能有效地使用 Rust丰泊。

性能

現(xiàn)在來(lái)做一個(gè)科學(xué)的比較。我首先生成三張不同尺寸的隨機(jī)圖像:1x1始绍、2000x2000瞳购、10000x10000。然后我測(cè)量每個(gè)(語(yǔ)言亏推、圖像大醒)組合的性能,每個(gè) diffimg 計(jì)算 10 次吞杭,然后取平均值盏浇,使用 time 命令的 real 值給出的值。diffimg-rs 使用–release 構(gòu)建芽狗,diffimg-go 使用 go build绢掰,而 Python diffimg 通過(guò) python3 - m diffimg 調(diào)用。以下是在 2015 年的 Macbook Pro 上獲得的結(jié)果:

圖像大小1x12000x200010000x10000

Rust0.001s0.490s5.871s

Go0.002s?(2x)0.756s?(1.54x)14.060s?(2.39x)

Python0.095s?(95x)1.419s?(2.90x)28.751s?(4.89x)

我損失了很多精度,因?yàn)?time 只精確到 10ms(因?yàn)橛?jì)算平均值的緣故滴劲,這里多顯示了一個(gè)數(shù)字)攻晒。該任務(wù)只需要一個(gè)非常特定類型的計(jì)算,所以不同的或更復(fù)雜的任務(wù)得出的數(shù)值可能差別很大班挖。話雖如此鲁捏,我們還是可以從這些數(shù)據(jù)中了解到一些東西。

對(duì)于 1x1 的圖像萧芙,幾乎所有的時(shí)間都花費(fèi)在設(shè)置中碴萧,沒(méi)有比例計(jì)算。Rust 獲勝末购,盡管它使用了兩個(gè)第三方庫(kù)(?clap??image?)破喻,Go 只使用了標(biāo)準(zhǔn)庫(kù)。我并不驚訝 Python 的啟動(dòng)那么緩慢盟榴,因?yàn)閷?dǎo)入大庫(kù) Pillow 是它的一個(gè)步驟曹质,time python -c ’ '其實(shí)只用了 0.030 秒。

對(duì)于 2000x2000 的圖像擎场,Go 和 Python 與 Rust 的差距就縮小了羽德,這大概是因?yàn)榕c計(jì)算相比,用于設(shè)置的時(shí)間更少迅办。然而宅静,對(duì)于 10000 年 x10000 的圖像,Rust 相比較之下性能更好站欺,我想這是由于其編譯器優(yōu)化所生成的機(jī)器代碼最幸碳小(循環(huán) 1 億次),設(shè)置時(shí)間相對(duì)就比較少了矾策。從不需要暫停進(jìn)行垃圾收集也是一個(gè)因素磷账。

Python 實(shí)現(xiàn)肯定還有很大的改進(jìn)余地,因?yàn)橄?Pillow 那么高效贾虽,我們?nèi)匀皇窃趦?nèi)存中創(chuàng)建一張差異圖像(遍歷輸入圖像)逃糟,然后累加每個(gè)像素的通道值。更直接的方法蓬豁,比如 Go 和 Rust 實(shí)現(xiàn)绰咽,可能會(huì)稍微快一些。然而地粪,純 Python 實(shí)現(xiàn)會(huì)非常慢取募,因?yàn)?Pillow 主要是用 C 完成其工作。因?yàn)榱硗鈨蓚€(gè)是純粹用一種語(yǔ)言實(shí)現(xiàn)的驶忌,這不是一個(gè)真正公平的比較矛辕,雖然在某些方面是笑跛,因?yàn)榈靡嬗?C 擴(kuò)展(Python 和 C 的關(guān)系一般都非常緊密 ),Python 有一大堆高性能庫(kù)可以使用聊品。

我還應(yīng)該提下二進(jìn)制文件的大蟹甚濉:Rust 的 2.1MB,使用–release 構(gòu)建翻屈,Go 的大小差不多陈哑,為 2.5 MB。Python 不創(chuàng)建二進(jìn)制文件伸眶,但是.pyc 文件有一定的可比性惊窖,和 diffimg 的.pyc 文件總共約 3 KB。它的源代碼也只有 3KB厘贼,但是包括 Pillow 依賴的話界酒,它將達(dá) 24MB。再說(shuō)一次嘴秸,這不是一個(gè)公平的比較毁欣,因?yàn)槲沂褂昧艘粋€(gè)第三方圖像庫(kù),但應(yīng)該提一下岳掐。

結(jié)論

顯然凭疮,這三種截然不同的語(yǔ)言實(shí)現(xiàn)滿足不同的細(xì)分市場(chǎng)需求。我經(jīng)常聽(tīng)到 Go 和 Rust 被一起提及串述,但我認(rèn)為执解,Go 和 Python 是兩種類似 / 存在競(jìng)爭(zhēng)關(guān)系的語(yǔ)言。它們都很適合編寫服務(wù)器端應(yīng)用程序邏輯(我在工作中大部分時(shí)間都在做這項(xiàng)工作)纲酗。僅比較原生代碼的性能衰腌,Go 完勝 Python,但許多有速度要求的 Python 庫(kù)是對(duì)速度更快的 C 實(shí)現(xiàn)的封裝——實(shí)際情況比這種天真的比較更復(fù)雜耕姊。編寫一個(gè)用于 Python 的 C 擴(kuò)展不能完全算是 Python 了(你需要了解 C)桶唐,但這個(gè)選項(xiàng)是對(duì)你開(kāi)放的。

對(duì)于你的后端服務(wù)器需求茉兰,Python 已被證明它對(duì)于大多數(shù)應(yīng)用程序都“足夠快”,但是如果你需要更好的性能欣簇,Go 可以规脸,Rust 更是如此,但是你要付出更多的開(kāi)發(fā)時(shí)間熊咽。Go 在這方面并沒(méi)有超出 Python 很多莫鸭,雖然開(kāi)發(fā)肯定是慢一些,這主要是由于其較小的特性集横殴。Rust 的特性非常齊全被因,但管理內(nèi)存總是比由語(yǔ)言自己管理會(huì)花費(fèi)更多的時(shí)間卿拴,這好過(guò)處理 Go 的極簡(jiǎn)性。

還應(yīng)該提一下梨与,世界上有很多很多 Python 開(kāi)發(fā)人員堕花,有些有幾十年的經(jīng)驗(yàn)。如果你選擇 Python粥鞋,那么找到更多有語(yǔ)言經(jīng)驗(yàn)的人加入到你的后端團(tuán)隊(duì)中可能并不難缘挽。然而,Go 開(kāi)發(fā)人員并不是特別少呻粹,而且很容易發(fā)展壕曼,因?yàn)檫@門語(yǔ)言很容易學(xué)習(xí)。由于 Rust 這種語(yǔ)言需要更長(zhǎng)的時(shí)間內(nèi)化等浊,所以開(kāi)發(fā)人員更少腮郊,也更難發(fā)展。

至于系統(tǒng)類型:靜態(tài)類型系統(tǒng)更容易編寫更多正確的代碼筹燕,但它不是萬(wàn)能的轧飞。無(wú)論使用何種語(yǔ)言,你仍然需要編寫綜合測(cè)試庄萎。它需要更多的訓(xùn)練踪少,但是我發(fā)現(xiàn),我使用 Python 編寫的代碼并不一定比 Go 更容易出錯(cuò)糠涛,只要我能夠編寫一個(gè)好的測(cè)試套件援奢。盡管如此,相比于 Go忍捡,我更喜歡 Rust 的類型系統(tǒng):它支持泛型集漾、模式匹配、錯(cuò)誤處理砸脊,它通常為你做得更多具篇。

最后,這種比較有點(diǎn)愚蠢凌埂,因?yàn)楸M管這些語(yǔ)言的用例重疊驱显,但它們占領(lǐng)著不同的細(xì)分市場(chǎng)。Python 開(kāi)發(fā)速度快瞳抓、性能低埃疫,而 Rust 恰恰相反,Go 則介于兩者之間孩哑。我喜歡 Python 和 Rust 超過(guò) Go(這可能令人奇怪)栓霜,不過(guò)我會(huì)繼續(xù)在工作中愉快地使用 Go(以及 Python),因?yàn)樗娴氖且环N構(gòu)建穩(wěn)定横蜒、可維護(hù)的應(yīng)用程序的偉大語(yǔ)言胳蛮,它有許多來(lái)自不同背景的貢獻(xiàn)者销凑。它的僵硬和極簡(jiǎn)主義使它使用起來(lái)不那么令人愉快(對(duì)我來(lái)說(shuō)),但這也正是它的力量所在仅炊。如果我要為一個(gè)新的 Web 應(yīng)用程序選擇后端語(yǔ)言的話斗幼,那將是 Go。

我對(duì)這三種語(yǔ)言所涵蓋的編程任務(wù)范圍相當(dāng)滿意——實(shí)際上茂洒,沒(méi)有哪個(gè)項(xiàng)目不能把它們中的一種視為很好的選擇孟岛。

英文原文:One Program Written in Python, Go督勺, and Rust


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末渠羞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子智哀,更是在濱河造成了極大的恐慌次询,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓷叫,死亡現(xiàn)場(chǎng)離奇詭異屯吊,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)摹菠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門盒卸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人次氨,你說(shuō)我怎么就攤上這事蔽介。” “怎么了煮寡?”我有些...
    開(kāi)封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵虹蓄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我幸撕,道長(zhǎng)薇组,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任坐儿,我火速辦了婚禮律胀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘貌矿。我一直安慰自己累铅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布站叼。 她就那樣靜靜地躺著,像睡著了一般菇民。 火紅的嫁衣襯著肌膚如雪尽楔。 梳的紋絲不亂的頭發(fā)上投储,一...
    開(kāi)封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音阔馋,去河邊找鬼玛荞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呕寝,可吹牛的內(nèi)容都是我干的勋眯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼下梢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼客蹋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起孽江,我...
    開(kāi)封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤讶坯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后岗屏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體辆琅,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年这刷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了婉烟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暇屋,死狀恐怖似袁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情率碾,我是刑警寧澤叔营,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站所宰,受9級(jí)特大地震影響绒尊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜仔粥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一婴谱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躯泰,春花似錦谭羔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至诵竭,卻和暖如春话告,著一層夾襖步出監(jiān)牢的瞬間兼搏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工沙郭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佛呻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓病线,卻偏偏與公主長(zhǎng)得像吓著,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子送挑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Python語(yǔ)言特性 1 Python的函數(shù)參數(shù)傳遞 看兩個(gè)如下例子绑莺,分析運(yùn)行結(jié)果: 代碼一: a = 1 def...
    伊森H閱讀 3,065評(píng)論 0 15
  • 2. 立體化戰(zhàn)爭(zhēng):把產(chǎn)品變成立體化的產(chǎn)品; 3. 敢為人先:機(jī)會(huì)出現(xiàn)的時(shí)候要摸著石頭過(guò)河让虐; 4. 小就是大:極簡(jiǎn)的...
    無(wú)門無(wú)派閱讀 817評(píng)論 0 0
  • List(列表) List(列表) 是 Python 中使用最頻繁的數(shù)據(jù)類型紊撕。列表可以完成大多數(shù)集合類的數(shù)據(jù)結(jié)構(gòu)實(shí)...
    b83920311d0f閱讀 252評(píng)論 0 0
  • 昨天就是一個(gè)夢(mèng) 我覺(jué)的昨天就是一個(gè)夢(mèng) T說(shuō)…好哲學(xué)、孩子的世界赡突,奇妙
    薇薇董閱讀 112評(píng)論 0 0
  • 何謂體制化惭缰? 我認(rèn)為“體制化”可以從字面上解釋浪南。“體制”便是各種人為制定的各種規(guī)章度漱受÷缭洌“化”字便是文化的意思。既然...
    feelinger21閱讀 3,149評(píng)論 3 5