幾年前,我負(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 看起來很有趣,甚至可能是一種有用的方法。這里有一些來自經(jīng)驗(yàn)的興趣點(diǎn),以及一些在工作中使用它的興趣點(diǎn)。 一、對(duì)比 Python 和 Go (代碼: diffimg (Python)和 diffimg-go ) 1、標(biāo)準(zhǔn)庫(kù):Go 有一個(gè)相當(dāng)不錯(cuò)的 image 標(biāo)準(zhǔn)庫(kù)模塊,以及命令行 flag 解析庫(kù)。我不需要尋找任何外部依賴;diffimg-go 實(shí)現(xiàn)沒有依賴,而 Python 實(shí)現(xiàn)使用了相當(dāng)重量級(jí)的第三方模塊(諷刺的是)Pillow。Go 的標(biāo)準(zhǔn)庫(kù)更有條理,而且經(jīng)過深思熟慮,而 Python 的會(huì)逐步發(fā)展,它們是多年來由許多作者創(chuàng)建的,有許多不同的約定。Go 標(biāo)準(zhǔn)庫(kù)的一致性使開發(fā)者更易于預(yù)測(cè)任何給定的模塊將如何發(fā)揮作用,而且源代碼有非常好的文檔記錄。 (1)使用標(biāo)準(zhǔn) image 庫(kù)的一個(gè)缺點(diǎn)是它不自動(dòng)檢測(cè)圖像是否有一個(gè) alpha 通道;所有圖像類型的像素值都有四個(gè)通道(RGBA)。因此,diffimg-go 實(shí)現(xiàn)要求用戶指明是否要使用 alpha 通道。這個(gè)小小的不便不值得找第三方庫(kù)來修復(fù)。 (2)一個(gè)很大的好處是,標(biāo)準(zhǔn)庫(kù)中有足夠的內(nèi)容,你不需要像 Django 這樣的 Web 框架。用 Go 有可能在沒有任何依賴關(guān)系的情況下建立一個(gè)真正可用的 Web 服務(wù)。Python 號(hào)稱“自帶電池(batteries-included)”,但在我看來,Go 做得更好。 2、靜態(tài)類型系統(tǒng):我過去使用靜態(tài)類型語(yǔ)言,但我過去幾年一直在使用 Python 編程。體驗(yàn)起初有點(diǎn)煩人,感覺它好像只是減慢了我的速度,它迫使我過度明確,即使我偶爾錯(cuò)了,Python 也會(huì)讓我做我想做的。有點(diǎn)像你給人發(fā)指令而對(duì)方總是打斷你讓你闡明你是什么意思,而有的人則總是點(diǎn)頭,看上去已經(jīng)理解你,但是你并不確定他們是否已經(jīng)全部了解。它將大大減少與類型相關(guān)的 Bug,但是我發(fā)現(xiàn),我仍然需要花幾乎相同的時(shí)間編寫測(cè)試。 (1)Go 的一個(gè)常見缺點(diǎn)是它沒有用戶可實(shí)現(xiàn)的泛型類型。雖然這不是一個(gè)構(gòu)建大型可擴(kuò)展應(yīng)用程序的必備特性,但它肯定會(huì)減緩開發(fā)速度。雖然已經(jīng)有替代模式建議,但是它們中沒有一個(gè)和真正的泛型類型一樣有效。 (2)靜態(tài)類型系統(tǒng)的一個(gè)優(yōu)點(diǎn)是,可以更簡(jiǎn)單快速地閱讀不熟悉的代碼庫(kù)。用好類型可以帶來許多動(dòng)態(tài)類型系統(tǒng)中會(huì)丟失的額外信息。 3、接口和結(jié)構(gòu):Go 使用接口和結(jié)構(gòu),而 Python 使用類。在我看來,這可能是最有趣的區(qū)別,因?yàn)樗仁刮覅^(qū)分定義行為的類型和保存信息的類型這兩個(gè)概念。Python 和其他傳統(tǒng)的面向?qū)ο蟮恼Z(yǔ)言都鼓勵(lì)你將它們混在一起,但這兩種范式各有利弊: (1)Go 強(qiáng)烈建議組合而不是繼承。雖然它通過嵌入繼承,不用類,但其數(shù)據(jù)和方法不是那么容易傳遞。通常,我認(rèn)為組合是更好的默認(rèn)模式,但我不是一個(gè)絕對(duì)主義者,在某些情況下繼承更合適,所以我不喜歡語(yǔ)言幫我作出這個(gè)決定。 (2)接口實(shí)現(xiàn)的分離意味著,如果有許多類型彼此相似,你就需要多次編寫類似的代碼。由于缺少泛型類型,在 Go 中,有些情況下我無法重用代碼,不過在 Python 中可以。 (3)然而,由于 Go 是靜態(tài)類型的,當(dāng)你編寫的代碼會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤時(shí),編譯器 / 源碼分析器(linter)會(huì)告訴你。Python 源碼分析器也可以有一點(diǎn)這樣的功能。但在 Python 中,當(dāng)你試圖訪問一個(gè)可能不存在的方法或?qū)傩詴r(shí),由于語(yǔ)言的動(dòng)態(tài)性,Python 源碼分析器無法確切地知道什么方法 / 屬性存在,直到運(yùn)行時(shí)。靜態(tài)定義的接口和結(jié)構(gòu)是唯一在編譯時(shí)和開發(fā)過程中知道什么可用的方法,這使得編譯時(shí)報(bào)錯(cuò)的 Go 比運(yùn)行時(shí)報(bào)錯(cuò)的 Python 更可靠。 4、沒有可選參數(shù):Go 只有可變函數(shù),類似于 Python 的關(guān)鍵字參數(shù),但不那么有用,因?yàn)閰?shù)需要是相同的類型。我發(fā)現(xiàn)關(guān)鍵字參數(shù)是我真正懷念的特性,這主要是你可以把任何類型的一個(gè) kwarg 扔給任何需要它的函數(shù),而無需重寫它的每一個(gè)調(diào)用,這讓重構(gòu)簡(jiǎn)單了許多。我在工作中經(jīng)常使用這個(gè)特性,它為我節(jié)省了很多時(shí)間。由于沒有該特性,這使得我在處理是否應(yīng)該基于命令行標(biāo)志創(chuàng)建差異圖像時(shí)顯得有些笨拙。 5、冗長(zhǎng):Go 有點(diǎn)冗長(zhǎng)(盡管不是像 Java 那么冗長(zhǎng))。這部分是因?yàn)槠漕愋拖到y(tǒng)沒有泛型,但主要是因?yàn)檎Z(yǔ)言本身很小,沒有提供很多特性(你只有一種循環(huán)結(jié)構(gòu)可以使用!)。我懷念Python 的列表推導(dǎo)式(list comprehensions)和其他函數(shù)式編程特性。如果你熟悉Python,你一兩天就可以學(xué)完 Tour of Go ,然后你就了解了整個(gè)語(yǔ)言。 6、錯(cuò)誤處理:Python 有異常,而 Go 在可能出錯(cuò)的地方通過從函數(shù)返回元組 value, error 來傳播錯(cuò)誤。Python 允許你在調(diào)用棧中的任何位置捕獲錯(cuò)誤,而不需要你一次又一次地手動(dòng)將錯(cuò)誤傳遞回去。這又使得代碼簡(jiǎn)潔,而不像 Go 那樣被其臭名昭著的 if err != nil 模式搞得雜亂無章,不過你需要弄清楚函數(shù)及其內(nèi)部的所有調(diào)用可能拋出的異常(使用 except Exception: 通常是一種糟糕的變通方案)。良好的文檔注釋和測(cè)試可以提供幫助,你在使用其中任何一種語(yǔ)言編程時(shí)都應(yīng)該添加。Go 的系統(tǒng)絕對(duì)更安全。如果你忽視了 err 值,你還是可以搬起石頭砸自己的腳,但該系統(tǒng)使糟糕的主意變得更明顯。 7、第三方模塊:在 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ù)中,這種奇怪的行為仍將很常見。 8、異步性:Goroutines 是啟動(dòng)異步任務(wù)的一種非常方便的方法。在 async/await 之前,Python 的異步解決方案有點(diǎn)令人沮喪。遺憾的是,我沒有用 Python 或 Go 編寫很多真實(shí)的異步代碼,而 diffimg 的簡(jiǎn)單性似乎并不適合說明異步性的額外開銷,所以我沒有太多要說的,雖然我確實(shí)喜歡使用 Go 的channels 來處理多個(gè)異步任務(wù)。我的理解是,對(duì)于性能,Go 仍然占了上風(fēng)了,因?yàn)?goroutine 可以充分利用多處理器并發(fā),而 Python 的基本 async/await 仍局限于一個(gè)處理器,所以主要用于 I / O 密集型任務(wù)。 9、調(diào)試:Python 勝出。pdb(以及像 ipdb 這樣更復(fù)雜的選項(xiàng))非常靈活,一旦你進(jìn)入 REPL,你可以編寫任何你想要的代碼。 Delve 是一個(gè)很好的調(diào)試器,但和直接放入解釋器不一樣,語(yǔ)言的全部功能都唾手可得。 Go 總結(jié) 我對(duì) Go 最初的印象是,由于它的抽象能力(有意為之)有限,所以它不像 Python 那樣有趣。Python 有更多的特性,因此有更多的方法來做一些事情,找到最快、最易讀或“最聰明”的解決方案可能會(huì)很有趣。Go 會(huì)積極地阻止你變得“聰明”。我認(rèn)為,Go 的優(yōu)勢(shì)在于它并不聰明。 它的極簡(jiǎn)主義和缺乏自由限制了單個(gè)開發(fā)人員實(shí)現(xiàn)一個(gè)想法。然而,當(dāng)項(xiàng)目擴(kuò)展到幾十個(gè)或幾百個(gè)開發(fā)人員時(shí),這個(gè)弱點(diǎn)變成了它的力量——因?yàn)槊總€(gè)人都使用同樣的語(yǔ)言特性小工具集,更容易統(tǒng)一,因此可以被他人理解。使用 Go 仍有可能編寫糟糕的代碼,但與那些更“強(qiáng)大”的語(yǔ)言相比,它讓你更難創(chuàng)造怪物。 使用一段時(shí)間后,我理解了為什么像谷歌這樣的公司想要一門這樣的語(yǔ)言。新工程師不斷地加入到大量代碼庫(kù)的開發(fā),在更復(fù)雜 / 更強(qiáng)大的語(yǔ)言中,在最后期限的壓力下,引入復(fù)雜性的速度比它被消除的速度更快。防止這種情況發(fā)生的最好方法是使用一種更不容易產(chǎn)生復(fù)雜性的語(yǔ)言。 所以說,我很高興工作在一個(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)讓人困惑,沒有足夠的背景信息,我不知道為什么要把所有這些限制強(qiáng)加給我,對(duì)于我想做的任務(wù)來說大而笨重。然而,自那時(shí)以來,我對(duì)程序執(zhí)行時(shí)內(nèi)存中發(fā)生了什么有了更多的了解。我從這本書開始,而不是試圖一頭扎進(jìn)去。這有很大的幫助,這份介紹比我見過的任何編程語(yǔ)言的介紹都要好。 在我讀過這本書的前十幾章之后,我覺得自己有足夠的信心嘗試 diffimg 的另一個(gè)實(shí)現(xiàn)(這時(shí),我覺得我的 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í),我注意到一些事情。 1、類型系統(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è)問題。盡管如此,這比我學(xué)習(xí) Go 的類型系統(tǒng)所用的時(shí)間要多得多,我還沒有適應(yīng)它的所有特性。 (1)有一個(gè)地方,因?yàn)轭愋拖到y(tǒng),我正在使用的圖像庫(kù)的實(shí)現(xiàn)會(huì)導(dǎo)致令人不快的代碼重復(fù)量。我最終只要匹配兩個(gè)最重要的枚舉類型,但匹配其他類型會(huì)導(dǎo)致另外半打左右?guī)缀跸嗤拇a行。在這個(gè)規(guī)模上,這不是一個(gè)問題,但它使我生氣。也許這里使用宏是個(gè)不錯(cuò)的主意,我仍然需要試驗(yàn)。 2、手動(dòng)內(nèi)存管理:Python 和 Go 會(huì)幫你撿垃圾。C 允許你亂丟垃圾,但當(dāng)它踩到你丟的香蕉皮時(shí),它會(huì)大發(fā)脾氣。Rust 會(huì)拍你一下,并要求你自己清理干凈。這會(huì)刺痛我,甚至超過了從動(dòng)態(tài)類型語(yǔ)言遷移到靜態(tài)類型語(yǔ)言,因?yàn)槲冶粚檳牧?,通常都是由語(yǔ)言跟在我后面撿。此外,編譯器會(huì)設(shè)法盡可能地幫助你,但你仍然需要大量的學(xué)習(xí)才能理解到底發(fā)生了什么。 (1)直接訪問內(nèi)存(以及 Rust 的函數(shù)式編程特性)的一個(gè)好處是,它簡(jiǎn)化了差異比的計(jì)算,因?yàn)槲铱梢院?jiǎn)單地映射原始字節(jié)數(shù)組,而不必按坐標(biāo)索引每個(gè)像素。 3、函數(shù)式特性:Rust 強(qiáng)烈鼓勵(lì)函數(shù)式的方法:它有 FP 友好的類型系統(tǒng)(類似 Haskell)、不可變類型、閉包,迭代器,模式匹配等,但也允許命令式代碼。它類似于編寫 OCaml(有趣的是,最初的 Rust 編譯器是用 OCaml 編寫的)。因此,對(duì)于這門與 C 競(jìng)爭(zhēng)的語(yǔ)言,代碼比你想象得更簡(jiǎn)潔。 4、錯(cuò)誤處理:不同于 Python 使用的異常模型,也不同于 Go 異常處理時(shí)返回的元組,Rust 利用枚舉類型:Result 返回 Ok(value) 或 Err(error)。這和 Go 的方式更接近,但更明確一些,而且利用了類型系統(tǒng)。還有一個(gè)語(yǔ)法糖用于檢查語(yǔ)句中的 Err 并提前返回:? 操作符(在我看來,Go 可以用類似這樣的東西)。 5、異步性:Rust 的 async/await 還沒有完全準(zhǔn)備好,但最終語(yǔ)法最近已經(jīng)達(dá)成一致。Rust 標(biāo)準(zhǔn)庫(kù)中也有一些基本的線程特性,看上去比 Python 的更容易使用,但我沒有花太多的時(shí)間了解它。Go 似乎仍然有最好的特性。 6、工具:rustup 和 cargo 分別是非常優(yōu)秀的語(yǔ)言版本管理器和包 / 模塊管理器實(shí)現(xiàn)。一切“正?!?。我特別喜歡自動(dòng)生成的文檔。對(duì)于這些工具,Python 提供的選項(xiàng)有點(diǎn)太簡(jiǎn)單,需要小心對(duì)待,正如我之前提到的,Go 有一種奇怪的管理模塊方式,但除此之外,它的工具比 Python 的更好。 7、編輯器插件:我的. vimrc 文件大到令人尷尬,至少有三十多個(gè)插件。我有一些用于代碼檢查、自動(dòng)補(bǔ)全以及格式化 Python 和 Go 的插件,但相比于其他兩種語(yǔ)言,Rust 插件更容易設(shè)置,更有用、更一致。 rust.vim 和 vim-lsp 插件(以及 Rust 語(yǔ)言服務(wù)器)是我獲得一個(gè)非常強(qiáng)大的配置所需要的全部。我還沒有測(cè)試其他編輯器,但是,借助 Rust 提供的編輯器無關(guān)的優(yōu)秀工具,我認(rèn)為它們一樣有幫助。這個(gè)設(shè)置提供了我使用過的最好的“轉(zhuǎn)到定義”。它可以很好地適用于本地、標(biāo)準(zhǔn)庫(kù)和開箱即用的第三方代碼。 8、調(diào)試:我還沒有嘗試過 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í)使用過那些調(diào)試器,那么其使用體驗(yàn)將是意料之中的。正如前面提到的,編譯器的錯(cuò)誤消息非常有幫助。 Rust 總結(jié) 你至少要讀過這本書的前幾章,否則我絕對(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í)的感覺而言,它非常有趣,就像 Python 一樣。它廣泛的特性使它很有表現(xiàn)力。雖然編譯器會(huì)經(jīng)常讓你停下,但它也非常有用,而且它對(duì)于如何解決你的借用問題 / 輸入問題的建議通常是有效的。正如我所提到的,這些工具是我遇到的所有語(yǔ)言中最好的,并且不像我使用的其他一些語(yǔ)言那樣給我?guī)砗芏嗦闊?。我非常喜歡使用這種語(yǔ)言,并將在 Python 的性能還不夠好的地方繼續(xù)尋找使用 Rust 的機(jī)會(huì)。 三、代碼示例 我提取了每個(gè) diffimg 中計(jì)算差異比的代碼塊。為了概括介紹 Python 的做法,這需要 Pillow 生成的差異圖像,對(duì)所有通道的所有像素值求和,并返回最大可能值(相同大小的純白圖像)除以總和的比值。 Python 對(duì)于 Go 和 Rust,方法有點(diǎn)不同:我們不用創(chuàng)建一個(gè)差異圖像,我們只要遍歷兩幅輸入圖像,并對(duì)每個(gè)像素的差異求和。在 Go 中,我們用坐標(biāo)索引每幅圖像…… Go ……但在 Rust 中,我們將圖像視為它們真的是在內(nèi)存中,是一系列可以壓縮到一起并消費(fèi)的字節(jié)。 Rust 對(duì)于這些例子,有一些事情需要注意:
這段是為了讓你了解需要掌握多少特定于語(yǔ)言的知識(shí)才能有效地使用 Rust。 四、性能 現(xiàn)在來做一個(gè)科學(xué)的比較。我首先生成三張不同尺寸的隨機(jī)圖像:1x1、2000x2000、10000x10000。然后我測(cè)量每個(gè)(語(yǔ)言、圖像大?。┙M合的性能,每個(gè) diffimg 計(jì)算 10 次,然后取平均值,使用 time 命令的 real 值給出的值。diffimg-rs 使用–release 構(gòu)建,diffimg-go 使用 go build,而 Python diffimg 通過 python3 - m diffimg 調(diào)用。以下是在 2015 年的 Macbook Pro 上獲得的結(jié)果: 我損失了很多精度,因?yàn)?time 只精確到 10ms(因?yàn)橛?jì)算平均值的緣故,這里多顯示了一個(gè)數(shù)字)。該任務(wù)只需要一個(gè)非常特定類型的計(jì)算,所以不同的或更復(fù)雜的任務(wù)得出的數(shù)值可能差別很大。話雖如此,我們還是可以從這些數(shù)據(jù)中了解到一些東西。 對(duì)于 1x1 的圖像,幾乎所有的時(shí)間都花費(fèi)在設(shè)置中,沒有比例計(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ī)器代碼最?。ㄑh(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)制文件的大?。篟ust 的 2.1MB,使用–release 構(gòu)建,Go 的大小差不多,為 2.5 MB。Python 不創(chuàng)建二進(jìn)制文件,但是.pyc 文件有一定的可比性,和 diffimg 的.pyc 文件總共約 3 KB。它的源代碼也只有 3KB,但是包括 Pillow 依賴的話,它將達(dá) 24MB。再說一次,這不是一個(gè)公平的比較,因?yàn)槲沂褂昧艘粋€(gè)第三方圖像庫(kù),但應(yīng)該提一下。 五、結(jié)論 顯然,這三種截然不同的語(yǔ)言實(shí)現(xiàn)滿足不同的細(xì)分市場(chǎng)需求。我經(jī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ì)你開放的。 對(duì)于你的后端服務(wù)器需求,Python 已被證明它對(duì)于大多數(shù)應(yīng)用程序都“足夠快”,但是如果你需要更好的性能,Go 可以,Rust 更是如此,但是你要付出更多的開發(fā)時(shí)間。Go 在這方面并沒有超出 Python 很多,雖然開發(fā)肯定是慢一些,這主要是由于其較小的特性集。Rust 的特性非常齊全,但管理內(nèi)存總是比由語(yǔ)言自己管理會(huì)花費(fèi)更多的時(shí)間,這好過處理 Go 的極簡(jiǎn)性。 還應(yīng)該提一下,世界上有很多很多 Python 開發(fā)人員,有些有幾十年的經(jīng)驗(yàn)。如果你選擇 Python,那么找到更多有語(yǔ)言經(jīng)驗(yàn)的人加入到你的后端團(tuán)隊(duì)中可能并不難。然而,Go 開發(fā)人員并不是特別少,而且很容易發(fā)展,因?yàn)檫@門語(yǔ)言很容易學(xué)習(xí)。由于 Rust 這種語(yǔ)言需要更長(zhǎng)的時(shí)間內(nèi)化,所以開發(fā)人員更少,也更難發(fā)展。 至于系統(tǒng)類型:靜態(tài)類型系統(tǒng)更容易編寫更多正確的代碼,但它不是萬能的。無論使用何種語(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 開發(fā)速度快、性能低,而 Rust 恰恰相反,Go 則介于兩者之間。我喜歡 Python 和 Rust 超過 Go(這可能令人奇怪),不過我會(huì)繼續(xù)在工作中愉快地使用 Go(以及 Python),因?yàn)樗娴氖且环N構(gòu)建穩(wěn)定、可維護(hù)的應(yīng)用程序的偉大語(yǔ)言,它有許多來自不同背景的貢獻(xiàn)者。它的僵硬和極簡(jiǎn)主義使它使用起來不那么令人愉快(對(duì)我來說),但這也正是它的力量所在。如果我要為一個(gè)新的 Web 應(yīng)用程序選擇后端語(yǔ)言的話,那將是 Go。 我對(duì)這三種語(yǔ)言所涵蓋的編程任務(wù)范圍相當(dāng)滿意——實(shí)際上,沒有哪個(gè)項(xiàng)目不能把它們中的一種視為很好的選擇。 查看英文原文:One Program Written in Python, Go, and Rust |
|