go如何減少編譯後的大小
A. 為什麼許多原本的 Java 項目都試圖用 go 進行重寫開源
項目推倒重構是項目開發大忌,一方面我們要盡量避免做項目推倒重構,盡量在前期就規劃好,另一方面,我們又希望項目能常做小重構,這對項目可持續性開發是很有幫助的。而語言的重構,把Java項目用Go語言重寫一遍,無疑是一次重大的推倒重來。
一、Go語言的優勢在哪裡
Go語言領先於Java的最大優勢,就在於快。Go語言會被編譯成機器代碼,直接執行;Java語言則使用JVM運行其代碼,這比Go語言要慢了很多。另外,Java語言的內存管理,相比於Go語言,也復雜得多,而內存管理,不管對於程序運行,還是對程序員的開發,都極為重要。最後,Go語言沒有引用只有指針,這比Java語言處處引用,又領先了一個身位。
B. c程序編譯後太大了怎麼辦啊
程序要執行當然不是只靠你寫的那兩行代碼,還需要好多庫,即使是很簡單的程序,你在VC++下按一下ALT+F7,選中Link就能看到了。編譯的時候這些庫也被包含到可執行文件當中,因此可執行文件較大。
Debug編譯後帶有源文件信息,因此可以調試,單步執行,而Release沒有這些信息,因此不能調試,但執行效率更高。
C. 如何有效減少Nios II EDS所編譯程式碼大小
1.使用Release模式
預設為debug模式,由於需要調試符號表,生成的debug文件會大一些。。選擇要設置的工程,右鍵,active build configuration 勾選 release即可。
2、調整GCC參數
選擇要設置的工程,右鍵——「properties" 左側選C/C++ build,在左側 total setting tab選擇general ,在右側Optimization Levels選Optimize most(-O3)或Optimize size(-Os)
3.使用Reced device drivers
選擇要設置的工程,按右鍵,System Library Properties ,將Reced device drivers打勾。
4.縮小Max file descriptors常數
預設為32 選擇好project,按右鍵,System Library Properties ,設置Max file descriptors常數。
5、將stdout、stderr、stdin設為null
在要設置的工程上,右鍵,System Library Properties ,將stdout、stderr、stdin設為null。
6、使用Small C library
右鍵System Library Properties ,將Small C library打勾。
7、使用UNIX-Style File I/O
Nios II的HAL已經提供了UNIX-Style File I/O函數,直接使用將可有效減少 編譯程序的文件大小。
8、自己製作 ANSI C函數
有些函數可以自己寫的。
9、使用Lightwieght device driver API
10.使用Minimal character-mode API
用sys/alt_stdio.h取代stdio.h ,sys/alt_stdio.h提供了alt_printf()、alt_putchar()、alt_putstr()與alt_getchar()如此可以減少程序大小。
11.移除不需要的驅動程序
這個需要自己手動移除。
12、避免Clean exit
按右鍵,System Library Properties ,不要選Clean exit(flush buffers)。
13、勾選Program never exits
按右鍵,System Library Properties ,勾選Program never exits
14、不要使用C++
按右鍵,System Library Properties ,不要勾選Support C++。
D. Go 語言內存管理(三):逃逸分析
Go 語言較之 C 語言一個很大的優勢就是自帶 GC 功能,可 GC 並不是沒有代價的。寫 C 語言的時候,在一個函數內聲明的變數,在函數退出後會自動釋放掉,因為這些變數分配在棧上。如果你期望變數的數據可以在函數退出後仍然能被訪問,就需要調用 malloc 方法在堆上申請內存,如果程序不再需要這塊內存了,再調用 free 方法釋放掉。Go 語言不需要你主動調用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變數,使用堆內存。編譯器的這個分析過程就叫做逃逸分析。
所以你在一個函數中通過 dict := make(map[string]int) 創建一個 map 變數,其背後的數據是放在棧空間上還是堆空間上,是不一定的。這要看編譯器分析的結果。
可逃逸分析並不是百分百准確的,它有缺陷。有的時候你會發現有些變數其實在棧空間上分配完全沒問題的,但編譯後程序還是把這些數據放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。
Go 語言雖然在內存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發,但如果你要在性能上較真的話,還是要掌握這些基礎知識。
這里不對堆內存和棧內存的區別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數的結束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:
這里舉一個小例子,來對比下堆棧的差別:
stack 函數中的變數 i 在函數退出會自動釋放;而 heap 函數返回的是對變數 i 的引用,也就是說 heap() 退出後,表示變數 i 還要能被訪問,它會自動被分配到堆空間上。
他們編譯出來的代碼如下:
邏輯的復雜度不言而喻,從上面的匯編中可看到, heap() 函數調用了 runtime.newobject() 方法,它會調用 mallocgc 方法從 mcache 上申請內存,申請的內部邏輯前面文章已經講述過。堆內存分配不僅分配上邏輯比棧空間分配復雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計算資源對其進行標記回收(也就是 GC 成本)。
Go 編輯器會自動幫我們找出需要進行動態分配的變數,它是在編譯時追蹤一個變數的生命周期,如果能確認一個數據只在函數空間內訪問,不會被外部使用,則使用棧空間,否則就要使用堆空間。
我們在 go build 編譯代碼時,可使用 -gcflags '-m' 參數來查看逃逸分析日誌。
以上面的兩個函數為例,編譯的日誌輸出是:
日誌中的 &i escapes to heap 表示該變數數據逃逸到了堆上。
需要使用堆空間,所以逃逸,這沒什麼可爭議的。但編譯器有時會將 不需要 使用堆空間的變數,也逃逸掉。這里是容易出現性能問題的大坑。網上有很多相關文章,列舉了一些導致逃逸情況,其實總結起來就一句話:
多級間接賦值容易導致逃逸 。
這里的多級間接指的是,對某個引用類對象中的引用類成員進行賦值。Go 語言中的引用類數據類型有 func , interface , slice , map , chan , *Type(指針) 。
記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數據類型,則會導致 Value 逃逸。這里的等號 = 不單單只賦值,也表示參數傳遞。
根據公式,我們假設一個變數 data 是以下幾種類型,相應的可以得出結論:
下面給出一些實際的例子:
如果變數值是一個函數,函數的參數又是引用類型,則傳遞給它的參數都會逃逸。
上例中 te 的類型是 func(*int) ,屬於引用類型,參數 *int 也是引用類型,則調用 te(&j) 形成了為 te 的參數(成員) *int 賦值的現象,即 te.i = &j 會導致逃逸。代碼中其他幾種調用都沒有形成 多級間接賦值 情況。
同理,如果函數的參數類型是 slice , map 或 interface{} 都會導致參數逃逸。
匿名函數的調用也是一樣的,它本質上也是一個函數變數。有興趣的可以自己測試一下。
只要使用了 Interface 類型(不是 interafce{} ),那麼賦值給它的變數一定會逃逸。因為 interfaceVariable.Method() 先是間接的定位到它的實際值,再調用實際值的同名方法,執行時實際值作為參數傳遞給方法。相當於 interfaceVariable.Method.this = realValue
向 channel 中發送數據,本質上就是為 channel 內部的成員賦值,就像給一個 slice 中的某一項賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導致發送到 channel 中的數據逃逸。
這本來也是情理之中的,發送給 channel 的數據是要與其他函數分享的,為了保證發送過去的指針依然可用,只能使用堆分配。
可變參數如 func(arg ...string) 實際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數逃逸的原因。
例子非常多,這里不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導致數據逃逸。這里加上 容易 二字是因為隨著語言的發展,相信這些問題會被慢慢解決,但現階段,這個可以作為我們分析逃逸現象的依據。
下面代碼中包含 2 種很常規的寫法,但他們卻有著很大的性能差距,建議自己想下為什麼。
Benchmark 和 pprof 給出的結果:
熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,並進行優化。
多級間接賦值會導致 Go 編譯器出現不必要的逃逸,在一些情況下可能我們只需要修改一下數據結構就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因為它會增加一級訪問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。
大多數情況下,性能優化都會為程序帶來一定的復雜度。建議實際項目中還是怎麼方便怎麼寫,功能完成後通過性能分析找到瓶頸所在,再對局部進行優化。