一.Xilinx Zynq-7000帶來新的設計思路
在以前,我們的單板上往往有CPU和多片FPGA,由CPU完成系統的配置和管理,FPGA完成特定算法的硬件加速,受限于CPU和FPGA之間的通信帶寬和延遲,CPU和FPGA之間的接口大多是用于配置和管理,無法傳輸大量的數據。
Xilinx推出的Zynq-7000系列芯片很好的解決了這一問題。它內含硬化好的CPU核和常見的外設(DRAM控制器,千兆以太網,USB 2.0 OTG,SD card控制器,FLASH控制器,UART,CAN,SPI,I2C等等 ),這一部分被稱為Processing System(簡稱PS),它可以完全獨立于FPGA運行;Zynq-7000芯片內部還有容量不等的FPGA資源,被稱為Programmable Logic(簡稱PL),可以支持不同復雜度的邏輯設計。最重要的是,在PS和PL之間,有超過3000根的互聯信號,包括9路AXI通道,可以提供大約100Gb/s的通信帶寬,同時在PS和PL之間還有DMA,Interrupt和EMIO等多種資源。這就使得數據可以在PS和PL之間靈活高效的遷移,從系統設計的角度上來講,任務可以在軟件和硬件之間靈活的分割,實現高度優化的系統設計。這也給嵌入式系統的開發方法提供了新的思路和流程:首先利用軟件可以快速靈活編程的特點,快速的用軟件實現系統的原型;然后通過對軟件進行profiling找出對系統性能影響最大的代碼,將這部分代碼用FPGA來硬件加速,實現高度優化的嵌入式系統;Xilinx還提供了HLS(High Level Synthesis)工具可以方便快速的把軟件代碼轉化成RTL代碼,幫助開發者快速的實現基于FPGA的硬件加速器。
在這一流程中,重要的一環是如何找出軟件中對性能影響最大的那部分代碼。對于簡單的應用,我們可以很容易的判斷出來,例如對頻譜分析來說,FFT算法就是最至關重要的需要優化的算法。但是在很多時候,軟件非常復雜,有很多的復雜的函數調用,很難通過靜態的觀察和分析找出對性能影響最大的那部分代碼,這時就需要通過profiling工具,在軟件動態運行中收集數據,通過統計的方法找出核心代碼了。
二.Profiling的對象
在Linux下有很多profiling工具,各自有自己的優勢和劣勢。在這里我們重點研究一下如何使用gprof對軟件做profiling。
很多介紹profiling工具的文章都是開發者自己寫一個簡單源文件,里面有簡單的函數調用。
為了更好的展示profiling的效果,這里我們沒有采用這種方法,而是采用了一個相對比較復雜的軟件包libjpeg。
libjpeg 是一個完全用C語言編寫的庫,包含了被廣泛使用的JPEG解碼、JPEG編碼和其他的JPEG功能的實現。這個庫由獨立JPEG工作組維護。編譯完成后除了相應的.a和.so庫文件之外,還會生成以下工具程序:
cjpeg和djpeg:用于JPEG的壓縮和解壓縮,可以和一些其他格式的圖形文件進行轉換。
rdjpgcom和wrjpgcom:用于在JFIF文件中插入和提取文字信息。
jpegtran:一個用于在不同的JPEG格式之間進行無損轉換的工具。
在這里cjpeg和djpeg就是很不錯的profiling對象,有一定的復雜度,但又沒有復雜到令人生畏。JPEG圖像文件可以在互聯網上靈活選取,基本原則是足夠大,這樣可以有比較長的運行時間來收集profiling數據,同時有足夠的細節可以讓軟件充分的運行起來。網站 上有很多大的圖片,筆者選擇的是一個2880x1800的JPEG文件。
Libjpeg可以在 上找到。這里使用的版本是13-Jan-2013發布的release 9。下載后的源文件是jpegsrc.v9.tar.gz
三. GNU profiler(gprof)簡介
GNU profiler(gprof)是GNU Binutils( https://sourceware.org/binutils/ )的一個組成部分,詳細的文檔可以在 https://sourceware.org/binutils/docs/gprof/ 找到,默認情況下Linux系統當中都帶有這個工具,不過如果打算在嵌入式開發板上用還是需要對GNU Binutils做交叉編譯的。
Gprof的功能:
1. 生成“flat profile”,包括每個函數的調用次數,每個函數消耗的處理器時間,
2. 生成“Call graph”,包括函數的調用關系,每個函數調用花費了多少時間。
3. 生成“注釋的源代碼”,即是程序源代碼的一個復本,標記有程序中每行代碼的執行次數。
Gprof的原理:
通過在編譯和鏈接時使用 -pg選項,gcc 在應用程序的每個函數中都加入了一個名為mcount (也可能是”_mcount”或者”__mcount”, 依賴于編譯器或操作系統)的函數,這樣應用程序里的每一個函數都會調用mcount, 而mcount 會在內存中保存一張函數調用圖,記錄通過函數調用堆棧找到的子函數和父函數的地址,以及所有與函數相關的調用時間,調用次數等信息。
Gprof基本使用流程
1. 在編譯和鏈接時加上-pg選項。一般可以加在 Makefile 中的CFLAGS和LDFLAGS中。
2. 執行編譯的二進制程序。執行參數和方式同以前。
3. 正常結束進程。這時內存中的信息會被寫入到程序運行目錄下的gmon.out 文件中。
4. 用 gprof 工具分析 gmon.out 文件。
Gprof參數說明
? -b 不再輸出統計圖表中每個字段的詳細描述。
? -p 只輸出函數的調用圖(Call graph的那部分信息)。
? -q 只輸出函數的時間消耗列表。
? -e Name 不輸出函數Name 及其子函數的調用圖(除非它們有未被限制的其它父函數)。可以給定多個-e 標志。一個 -e 標志只能指定一個函數。
? -E Name 不輸出函數Name 及其子函數的調用圖,此標志類似于 -e 標志,但它在總時間和百分比時間的計算中排除了由函數Name 及其子函數所用的時間。
? -f Name 輸出函數Name 及其子函數的調用圖。可以指定多個 -f 標志。一個 -f 標志只能指定一個函數。
? -F Name 輸出函數Name 及其子函數的調用圖,它類似于 -f 標志,但它在總時間和百分比時間計算中僅使用所打印的例程的時間。可以指定多個 -F 標志。一個 -F 標志只能指定一個函數。-F 標志覆蓋 -E 標志。
一般用法:
gprof -b ELF_file_name gmon.out >report.txt
Gprof報告中flat profile表格各列的說明:
%time: 該函數消耗時間占程序所有時間百分比,全部相加應該是100%。
Cumulative seconds: 程序的累積執行時間,包括表格內該函數所在行之上的所有函數的執行時間
Self Seconds: 該函數本身的全部執行時間。表格會依照這列的數值按照降序排序所有行
Calls: 函數被調用次數, 如果無法確定則為空。
Self ms/call: 函數平均執行時間。
Total ms/call: 函數平均執行時間, 包括其內部調用。
Name: 函數名。在按照self seconds和calls排序后再依照這列進行字母排序。
Gprof報告中Call Graph表格各列的說明:
Index: 索引值
%time: 函數消耗時間占所有時間百分比
Self: 函數本身執行時間
Children: 執行子函數所用時間
Called: 被調用次數
Name: 函數名
Gprof的優勢:
1. 簡單易用。只需要在編譯和鏈接是增加-pg選項。gprof對于代碼大部分是用戶空間的CPU密集型的應用程序用處明顯,對于大部分時間運行在內核空間或者由于外部因素(例如操作系統的 I/O 子系統過載)而運行緩慢的應用程序則意義不大。
2. GNU Binutils的組成部分,基本上任何Linux里面都有。可以把生成的gmon.out拷貝到host上進行分析,省掉了一部分交叉編譯的工作量。
Gprof的劣勢:
1. Gprof只夠監控到編譯和鏈接時有-pg選項的函數,工作在內核態的函數和沒有加-pg編譯的第三方庫函數是無法被gprof監控到的。因此Gprof比較適合執行時間大部分在用戶態的應用。在使用Gprof前最好用Linux下的time命令來確認應用程序的實際運行時間、用戶空間運行時間、內核空間運行時間,以判斷是否合適用gprof。Oprofile可以解決這一問題。
2. Gprof不能監控shared library,即.so的文件。
對此有詳細的分析。對這類文件可以用sprof,不過并不好用。變通的辦法是將library靜態鏈接到應用中,這樣會增加應用程序的code size。
3. Gprof 不支持多線程應用,多線程下只能采集主線程性能數據。原因是在多線程內只有主線程才能響應gprof采用的ITIMER_PROF信號。有一個簡單的方法可以解決這一問題:
4. gprof只能在程序正常結束退出,或者通過系統調用exit()退出之后才能生成報告(gmon.out)。原因是gprof通過在atexit()里注冊了一個函數來產生結果信息,任何非正常退出都不會執行atexit()的動作,所以不會產生gmon.out文件。
5. 函數執行時間是估計值。函數執行時間是通過采樣估算的, 在執行時間足夠長的情況下,這個不是什么大的問題,一般估算值與實際值相差不大。
評論
查看更多