對于嵌入式開發團隊來說,更快的嵌入式應用上市時間確實是一個重要的考慮因素。但是,如何開發一種方法來更快地交付應用程序,同時將質量和安全性作為優先事項?在這種情況下,OTA軟件更新經理Mender.io背后的產品工程團隊經歷了為開發Mender的嵌入式客戶端和服務器部分選擇最佳編程語言的過程。在這個評估過程中,Go、C和C++入圍候選名單。本文解釋了從這個評估過程中吸取的一些教訓,以及為什么最終選擇Go開發Mender的客戶端嵌入式應用程序。
雖然這可能很主觀,但Go對于嵌入式開發來說是一種非常有效的語言。當涉及到網絡編程時,尤其如此,網絡編程在某種程度上是每個連接的設備或應用程序的一部分。Go是谷歌為滿足谷歌開發者的需求而創建的,它的開發主要是為了解決其生態系統中的復雜性爆炸問題。因此,高效編譯、高效執行和易于編程是開發Go背后的三個主要原則,因為這三個原則以前并不是在同一種主流語言中都可用。
然而,我們要強調的是,Go不能被視為C的替代品,因為在很多地方都需要C,例如在開發實時操作系統或設備驅動程序時。
嵌入式開發的嚴格要求
建立架構后,修補程序產品工程團隊評估哪種語言最適合開發修補程序應用程序。該系統應該由運行在嵌入式設備上的客戶端和作為客戶端連接的中心點的服務器組成。因此,我們對語言有幾個要求:
由于客戶端應用程序將在嵌入式設備上運行,編譯后的二進制文件需要盡可能小。
它需要與Linux的嵌入式發行版Yocto一起工作。
安裝客戶端的復雜性應該很低,從而限制外部依賴性和庫。
因為它可以在不同的設備上運行,所以該語言必須在不同的架構上編譯。
運行修補程序客戶端應用程序的設備將是物聯網或聯網設備,因此該語言需要訪問聯網庫。
選擇新語言的要求還包括一些非功能性要求:
我們組織中盡可能多的程序員需要能夠理解這種語言。
在用C編寫的現有應用程序之間共享和重用現有代碼以及在客戶端和服務器應用程序之間重用代碼必須盡可能容易。
開發速度也是考慮因素之一——我們面臨著快速添加新功能的持續壓力。
比較Go與C
Go還因為其極其豐富的標準庫而被選中進行嵌入式開發,這使得開發速度更快,尤其是與C相比。Go繼承了C、C++和Python的許多元素,包括表達式語法、控制流語句、數據結構、接口、指針、按值傳遞概念、參數解析語法、字符串處理和垃圾收集。通過其優化的編譯器,Go可以在嵌入式設備上自然運行。
Go確實有一些缺點,它們的分量不足以克服它的優勢,比如開發速度和語言構建的簡易性,但它們確實是我們決策的考慮因素。
Go與C的大小比較
就大小而言,Go比C更笨重,這是它的幾個缺點之一。如果你把“helloworld”作為最小的應用程序,你可以在Go中編寫并使用內置的println函數,在去掉調試符號后,它的大小剛好超過600kB。如果包含fmt包及其依賴項,那么大小將增加到1.5MB。
與C相比,如果你正在構建一個動態鏈接庫,那么它僅為8kB。如果是靜態的,那么大小會增加到800KB以上,這比Go二進制文件還要大得驚人。
Go與C的速度比較
編譯的Go代碼通常比C可執行文件慢。Go是完全垃圾收集的,這本身會減慢速度。使用C,你可以精確地決定要為變量分配內存的位置,以及是在堆棧上還是在堆上。在Go中,編譯器試圖對變量的分配位置做出明智的決定。例如,你可以看到變量將被分配到哪里(go build-gcflags-m),但不能強制編譯器僅使用堆棧。
然而,說到速度,我們不能忘記編譯速度和嵌入式開發人員速度。Go提供了極快的編譯速度;例如,15000行Go客戶端代碼需要1.4秒才能編譯。Go非常適合并發執行(goroutines和channel),前面提到的豐富標準庫涵蓋了大多數基本需求,因此開發速度更快。
匯編和交叉匯編
有兩個Go編譯器可以使用:最初的一個叫做gc。它是默認安裝的一部分,由Google編寫和維護。第二個叫做gccgo,是GCC的前身。使用gccgo,編譯速度極快,大型模塊可以在幾秒內編譯完成。如前所述,Go默認情況下是靜態編譯的,因此只創建一個二進制文件,而不需要額外的依賴關系或虛擬機。可以使用-linkshared標志創建和使用共享庫。通常,Go不需要構建文件,可以通過一個簡單的“Go build”命令進行構建,但也可以將Makefile用于更高級的構建過程。
除此之外,在交叉編譯方面,Go支持大量的操作系統和體系結構。
Go中的調試和測試
許多開發人員為了在程序執行時了解程序內部的情況,將使用GDB。對于高度并發的Go應用程序,GDB在調試它們時遇到了一些問題。幸運的是,有一個名為Delve的專用Go調試器更適合于此目的。只熟悉GDB的嵌入式開發人員應該能夠在大多數情況下使用它而不會出現任何問題。
將測試和單元測試添加到Go代碼中非常容易。測試內置于該語言中,只需創建一個帶有“test”后綴的文件,向函數添加測試前綴,導入測試包,然后運行“go test”即可。所有測試都將自動從源代碼中提取并相應執行。
并發支持
Go中很好地支持并發。它有兩個內置機制:goroutine和channel。goroutine是輕量級線程,大小只有2kB。創建goroutine非常簡單:只需在函數前面添加“go”關鍵字,它就會同時執行。Go有自己的內部調度器,goroutine可以根據需要復用到OS線程中。通道是在gorroutine之間交換消息的“管道”,可以是阻塞的,也可以是非阻塞的。
Go利大于弊
我們使用Go的經驗顯示了優點和缺點。從負面來說,社區中有很多外部庫,但質量參差不齊,你需要非常小心地使用哪種庫。隨著語言的成熟和被采用,這一點正在大大提高。
當你的Go代碼中有一些C綁定時,事情通常會變得更加復雜,特別是交叉編譯,如果不導入整個C交叉編譯基礎結構,就無法輕松完成。
使用Go進行嵌入式開發仍然有很多好處。從C和/或Python轉換到Go是快速而簡單的,并在幾天內實現高效。Go提供了非常好的工具和100多個包的標準庫。你可以輕松地設置和控制運行時設置,例如要使用多少OS線程(GOMAXPROCS),或者是否要打開或關閉垃圾收集(GOGC=off)。庫和代碼可以很容易地在服務器和客戶端開發團隊之間共享。