一.Xilinx Zynq-7000帶來新的設(shè)計(jì)思路
在以前,我們的單板上往往有CPU和多片FPGA,由CPU完成系統(tǒng)的配置和管理,F(xiàn)PGA完成特定算法的硬件加速,受限于CPU和FPGA之間的通信帶寬和延遲,CPU和FPGA之間的接口大多是用于配置和管理,無法傳輸大量的數(shù)據(jù)。
Xilinx推出的Zynq-7000系列芯片很好的解決了這一問題。它內(nèi)含硬化好的CPU核和常見的外設(shè)(DRAM控制器,千兆以太網(wǎng),USB 2.0 OTG,SD card控制器,F(xiàn)LASH控制器,UART,CAN,SPI,I2C等等 ),這一部分被稱為Processing System(簡(jiǎn)稱PS),它可以完全獨(dú)立于FPGA運(yùn)行;Zynq-7000芯片內(nèi)部還有容量不等的FPGA資源,被稱為Programmable Logic(簡(jiǎn)稱PL),可以支持不同復(fù)雜度的邏輯設(shè)計(jì)。最重要的是,在PS和PL之間,有超過3000根的互聯(lián)信號(hào),包括9路AXI通道,可以提供大約100Gb/s的通信帶寬,同時(shí)在PS和PL之間還有DMA,Interrupt和EMIO等多種資源。這就使得數(shù)據(jù)可以在PS和PL之間靈活高效的遷移,從系統(tǒng)設(shè)計(jì)的角度上來講,任務(wù)可以在軟件和硬件之間靈活的分割,實(shí)現(xiàn)高度優(yōu)化的系統(tǒng)設(shè)計(jì)。這也給嵌入式系統(tǒng)的開發(fā)方法提供了新的思路和流程:首先利用軟件可以快速靈活編程的特點(diǎn),快速的用軟件實(shí)現(xiàn)系統(tǒng)的原型;然后通過對(duì)軟件進(jìn)行profiling找出對(duì)系統(tǒng)性能影響最大的代碼,將這部分代碼用FPGA來硬件加速,實(shí)現(xiàn)高度優(yōu)化的嵌入式系統(tǒng);Xilinx還提供了HLS(High Level Synthesis)工具可以方便快速的把軟件代碼轉(zhuǎn)化成RTL代碼,幫助開發(fā)者快速的實(shí)現(xiàn)基于FPGA的硬件加速器。
在這一流程中,重要的一環(huán)是如何找出軟件中對(duì)性能影響最大的那部分代碼。對(duì)于簡(jiǎn)單的應(yīng)用,我們可以很容易的判斷出來,例如對(duì)頻譜分析來說,F(xiàn)FT算法就是最至關(guān)重要的需要優(yōu)化的算法。但是在很多時(shí)候,軟件非常復(fù)雜,有很多的復(fù)雜的函數(shù)調(diào)用,很難通過靜態(tài)的觀察和分析找出對(duì)性能影響最大的那部分代碼,這時(shí)就需要通過profiling工具,在軟件動(dòng)態(tài)運(yùn)行中收集數(shù)據(jù),通過統(tǒng)計(jì)的方法找出核心代碼了。
二.Profiling的對(duì)象
在Linux下有很多profiling工具,各自有自己的優(yōu)勢(shì)和劣勢(shì)。在這里我們重點(diǎn)研究一下如何使用gprof對(duì)軟件做profiling。
很多介紹profiling工具的文章都是開發(fā)者自己寫一個(gè)簡(jiǎn)單源文件,里面有簡(jiǎn)單的函數(shù)調(diào)用。
為了更好的展示profiling的效果,這里我們沒有采用這種方法,而是采用了一個(gè)相對(duì)比較復(fù)雜的軟件包libjpeg。
libjpeg 是一個(gè)完全用C語言編寫的庫,包含了被廣泛使用的JPEG解碼、JPEG編碼和其他的JPEG功能的實(shí)現(xiàn)。這個(gè)庫由獨(dú)立JPEG工作組維護(hù)。編譯完成后除了相應(yīng)的.a和.so庫文件之外,還會(huì)生成以下工具程序:
cjpeg和djpeg:用于JPEG的壓縮和解壓縮,可以和一些其他格式的圖形文件進(jìn)行轉(zhuǎn)換。
rdjpgcom和wrjpgcom:用于在JFIF文件中插入和提取文字信息。
jpegtran:一個(gè)用于在不同的JPEG格式之間進(jìn)行無損轉(zhuǎn)換的工具。
在這里cjpeg和djpeg就是很不錯(cuò)的profiling對(duì)象,有一定的復(fù)雜度,但又沒有復(fù)雜到令人生畏。JPEG圖像文件可以在互聯(lián)網(wǎng)上靈活選取,基本原則是足夠大,這樣可以有比較長(zhǎng)的運(yùn)行時(shí)間來收集profiling數(shù)據(jù),同時(shí)有足夠的細(xì)節(jié)可以讓軟件充分的運(yùn)行起來。網(wǎng)站 上有很多大的圖片,筆者選擇的是一個(gè)2880x1800的JPEG文件。
Libjpeg可以在 上找到。這里使用的版本是13-Jan-2013發(fā)布的release 9。下載后的源文件是jpegsrc.v9.tar.gz
三. GNU profiler(gprof)簡(jiǎn)介
GNU profiler(gprof)是GNU Binutils( https://sourceware.org/binutils/ )的一個(gè)組成部分,詳細(xì)的文檔可以在 https://sourceware.org/binutils/docs/gprof/ 找到,默認(rèn)情況下Linux系統(tǒng)當(dāng)中都帶有這個(gè)工具,不過如果打算在嵌入式開發(fā)板上用還是需要對(duì)GNU Binutils做交叉編譯的。
Gprof的功能:
1. 生成“flat profile”,包括每個(gè)函數(shù)的調(diào)用次數(shù),每個(gè)函數(shù)消耗的處理器時(shí)間,
2. 生成“Call graph”,包括函數(shù)的調(diào)用關(guān)系,每個(gè)函數(shù)調(diào)用花費(fèi)了多少時(shí)間。
3. 生成“注釋的源代碼”,即是程序源代碼的一個(gè)復(fù)本,標(biāo)記有程序中每行代碼的執(zhí)行次數(shù)。
Gprof的原理:
通過在編譯和鏈接時(shí)使用 -pg選項(xiàng),gcc 在應(yīng)用程序的每個(gè)函數(shù)中都加入了一個(gè)名為mcount (也可能是”_mcount”或者”__mcount”, 依賴于編譯器或操作系統(tǒng))的函數(shù),這樣應(yīng)用程序里的每一個(gè)函數(shù)都會(huì)調(diào)用mcount, 而mcount 會(huì)在內(nèi)存中保存一張函數(shù)調(diào)用圖,記錄通過函數(shù)調(diào)用堆棧找到的子函數(shù)和父函數(shù)的地址,以及所有與函數(shù)相關(guān)的調(diào)用時(shí)間,調(diào)用次數(shù)等信息。
Gprof基本使用流程
1. 在編譯和鏈接時(shí)加上-pg選項(xiàng)。一般可以加在 Makefile 中的CFLAGS和LDFLAGS中。
2. 執(zhí)行編譯的二進(jìn)制程序。執(zhí)行參數(shù)和方式同以前。
3. 正常結(jié)束進(jìn)程。這時(shí)內(nèi)存中的信息會(huì)被寫入到程序運(yùn)行目錄下的gmon.out 文件中。
4. 用 gprof 工具分析 gmon.out 文件。
Gprof參數(shù)說明
? -b 不再輸出統(tǒng)計(jì)圖表中每個(gè)字段的詳細(xì)描述。
? -p 只輸出函數(shù)的調(diào)用圖(Call graph的那部分信息)。
? -q 只輸出函數(shù)的時(shí)間消耗列表。
? -e Name 不輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖(除非它們有未被限制的其它父函數(shù))。可以給定多個(gè)-e 標(biāo)志。一個(gè) -e 標(biāo)志只能指定一個(gè)函數(shù)。
? -E Name 不輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖,此標(biāo)志類似于 -e 標(biāo)志,但它在總時(shí)間和百分比時(shí)間的計(jì)算中排除了由函數(shù)Name 及其子函數(shù)所用的時(shí)間。
? -f Name 輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖。可以指定多個(gè) -f 標(biāo)志。一個(gè) -f 標(biāo)志只能指定一個(gè)函數(shù)。
? -F Name 輸出函數(shù)Name 及其子函數(shù)的調(diào)用圖,它類似于 -f 標(biāo)志,但它在總時(shí)間和百分比時(shí)間計(jì)算中僅使用所打印的例程的時(shí)間。可以指定多個(gè) -F 標(biāo)志。一個(gè) -F 標(biāo)志只能指定一個(gè)函數(shù)。-F 標(biāo)志覆蓋 -E 標(biāo)志。
一般用法:
gprof -b ELF_file_name gmon.out >report.txt
Gprof報(bào)告中flat profile表格各列的說明:
%time: 該函數(shù)消耗時(shí)間占程序所有時(shí)間百分比,全部相加應(yīng)該是100%。
Cumulative seconds: 程序的累積執(zhí)行時(shí)間,包括表格內(nèi)該函數(shù)所在行之上的所有函數(shù)的執(zhí)行時(shí)間
Self Seconds: 該函數(shù)本身的全部執(zhí)行時(shí)間。表格會(huì)依照這列的數(shù)值按照降序排序所有行
Calls: 函數(shù)被調(diào)用次數(shù), 如果無法確定則為空。
Self ms/call: 函數(shù)平均執(zhí)行時(shí)間。
Total ms/call: 函數(shù)平均執(zhí)行時(shí)間, 包括其內(nèi)部調(diào)用。
Name: 函數(shù)名。在按照self seconds和calls排序后再依照這列進(jìn)行字母排序。
Gprof報(bào)告中Call Graph表格各列的說明:
Index: 索引值
%time: 函數(shù)消耗時(shí)間占所有時(shí)間百分比
Self: 函數(shù)本身執(zhí)行時(shí)間
Children: 執(zhí)行子函數(shù)所用時(shí)間
Called: 被調(diào)用次數(shù)
Name: 函數(shù)名
Gprof的優(yōu)勢(shì):
1. 簡(jiǎn)單易用。只需要在編譯和鏈接是增加-pg選項(xiàng)。gprof對(duì)于代碼大部分是用戶空間的CPU密集型的應(yīng)用程序用處明顯,對(duì)于大部分時(shí)間運(yùn)行在內(nèi)核空間或者由于外部因素(例如操作系統(tǒng)的 I/O 子系統(tǒng)過載)而運(yùn)行緩慢的應(yīng)用程序則意義不大。
2. GNU Binutils的組成部分,基本上任何Linux里面都有。可以把生成的gmon.out拷貝到host上進(jìn)行分析,省掉了一部分交叉編譯的工作量。
Gprof的劣勢(shì):
1. Gprof只夠監(jiān)控到編譯和鏈接時(shí)有-pg選項(xiàng)的函數(shù),工作在內(nèi)核態(tài)的函數(shù)和沒有加-pg編譯的第三方庫函數(shù)是無法被gprof監(jiān)控到的。因此Gprof比較適合執(zhí)行時(shí)間大部分在用戶態(tài)的應(yīng)用。在使用Gprof前最好用Linux下的time命令來確認(rèn)應(yīng)用程序的實(shí)際運(yùn)行時(shí)間、用戶空間運(yùn)行時(shí)間、內(nèi)核空間運(yùn)行時(shí)間,以判斷是否合適用gprof。Oprofile可以解決這一問題。
2. Gprof不能監(jiān)控shared library,即.so的文件。
對(duì)此有詳細(xì)的分析。對(duì)這類文件可以用sprof,不過并不好用。變通的辦法是將library靜態(tài)鏈接到應(yīng)用中,這樣會(huì)增加應(yīng)用程序的code size。
3. Gprof 不支持多線程應(yīng)用,多線程下只能采集主線程性能數(shù)據(jù)。原因是在多線程內(nèi)只有主線程才能響應(yīng)gprof采用的ITIMER_PROF信號(hào)。有一個(gè)簡(jiǎn)單的方法可以解決這一問題:
4. gprof只能在程序正常結(jié)束退出,或者通過系統(tǒng)調(diào)用exit()退出之后才能生成報(bào)告(gmon.out)。原因是gprof通過在atexit()里注冊(cè)了一個(gè)函數(shù)來產(chǎn)生結(jié)果信息,任何非正常退出都不會(huì)執(zhí)行atexit()的動(dòng)作,所以不會(huì)產(chǎn)生gmon.out文件。
5. 函數(shù)執(zhí)行時(shí)間是估計(jì)值。函數(shù)執(zhí)行時(shí)間是通過采樣估算的, 在執(zhí)行時(shí)間足夠長(zhǎng)的情況下,這個(gè)不是什么大的問題,一般估算值與實(shí)際值相差不大。
四.Gprof在Zynq-7000開發(fā)板上的實(shí)驗(yàn):
Hardware: ZC706 evaluation board(其他開發(fā)板亦可,只是細(xì)節(jié)上會(huì)略有不同)
Software: Xilinx 14.7 Linux pre-built
Tool chain: PetaLinux 2013.04 tool chain
為了簡(jiǎn)單起見,筆者沒有重新編譯Linux,而是使用的Xilinx 14.7 Linux pre-built。
在Linux Host上下載libjpeg后執(zhí)行以下命令即可完成編譯:
cd
tar zxvf /path/to/jpegsrc.v9.tar.gz
cd jpeg-9
./configure --prefix=/home/wave/xilinx/libjpeg/jpeg-bin --host=arm-xilinx-linux-gnueabi
Note: 參數(shù)--prefix指明編譯結(jié)果的安裝位置,參數(shù)--host指明交叉編譯工具鏈的前綴。在使用PetaLinux 2013.04 tool chain之前需要先到其目錄下source settings.sh
這里需要編輯Makefile,在CFLAGS和LDFLAGS中增加-pg選項(xiàng)。
make
make install
這時(shí)編譯完成后的可執(zhí)行程序cjpeg和djpeg使用到了.so文件,不適合用gprof。關(guān)于這一點(diǎn)可以用ldd命令確認(rèn)。所以還需要用以下命令手工編譯出statically linked binary,拷貝到libjpeg安裝目錄下的bin目錄下,方便后期的profiling。這些命令可以通過觀察libjpeg的make過程得到。
arm-xilinx-linux-gnueabi-gcc -std=gnu99 -g -O2 -pg -o djpeg-s djpeg.c wrppm.c wrgif.c wrtarga.c wrrle.c wrbmp.c rdcolmap.c cdjpeg.c ../jpeg-bin/lib/libjpeg.a
arm-xilinx-linux-gnueabi-gcc -std=gnu99 -g -O2 -pg -o cjpeg-s cjpeg.c rdppm.c rdgif.c rdtarga.c rdrle.c rdbmp.c rdswitch.c cdjpeg.c ../jpeg-bin/lib/libjpeg.a
然后將jpeg-bin下的所有內(nèi)容打包,和Xilinx 14.7 Linux pre-built image files,以及數(shù)據(jù)文件park-2880x1800.jpg拷貝到SD卡中,從SD卡啟動(dòng)ZC706開發(fā)板。
在開發(fā)板的console上,執(zhí)行以下命令
mount /dev/mmcblk0p1 /mnt
mkdir work
cd work
tar zxvf /mnt/jpeg-bin.tar.gz
cd jpeg-bin/bin
cp /mnt/park-2880x1800.jpg .
export LD_LIBRARY_PATH=/home/root/work/jpeg-bin/lib
time ./djpeg-s -bmp park-2880x1800.jpg > result.bmp
mv gmon.out gmon-ds.out
time ./cjpeg-s ./result.bmp > ./result.jpg
mv gmon.out gmon-cs.out
對(duì)于djpeg-s和cjpeg-s,執(zhí)行時(shí)間如下所示。我們可以看到這兩個(gè)應(yīng)用程序的主要執(zhí)行時(shí)間實(shí)在用戶空間的,還是比較適合用gprof來做profiling的。
real 0m4.258s
user 0m4.200s
sys 0m0.050s
real 0m4.289s
user 0m4.230s
sys 0m0.050s
然后我們可以把執(zhí)行結(jié)果拷貝到SD卡中,準(zhǔn)備拿到Linux Host上進(jìn)行分析。
cp result.* /mnt
cp gmon*.out /mnt
umount /mnt
在Linux Host上,我們可以通過以下命令看到profiling的結(jié)果:
gprof -b djpeg-s gmon-ds.out >report-ds.txt
gprof -b cjpeg-s gmon-cs.out >report-cs.txt
關(guān)于JPEG解碼Profiling結(jié)果的主要部分如下:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
20.95 0.31 0.31 1800 0.17 0.17 ycc_rgb_convert
19.60 0.60 0.29 40680 0.01 0.01 jpeg_idct_16x16
17.57 0.86 0.26 20340 0.01 0.02 decode_mcu
14.87 1.08 0.22 81000 0.00 0.00 jpeg_idct_islow
13.51 1.28 0.20 finish_output_bmp
6.08 1.37 0.09 1175224 0.00 0.00 jpeg_fill_bit_buffer
5.41 1.45 0.08 put_pixel_rows
2.03 1.48 0.03 127506 0.00 0.00 jpeg_huff_decode
關(guān)于JPEG編碼Profiling結(jié)果的主要部分如下:
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
24.63 0.50 0.50 preload_image
21.18 0.93 0.43 20340 0.02 0.02 encode_mcu_huff
13.79 1.21 0.28 40680 0.01 0.01 jpeg_fdct_16x16
12.81 1.47 0.26 81180 0.00 0.01 forward_DCT
8.87 1.65 0.18 81000 0.00 0.00 jpeg_fdct_islow
8.37 1.82 0.17 1800 0.09 0.09 rgb_ycc_convert
5.42 1.93 0.11 1 110.00 110.00 get_24bit_row
3.45 2.00 0.07 __divsi3
0.49 2.01 0.01 113 0.09 10.27 compress_data
0.49 2.02 0.01 __aeabi_uidivmod
0.49 2.03 0.01 jpeg_fdct_ifast
怎么樣?是不是很容易?
如果反復(fù)profiling幾次,就會(huì)注意到profiling結(jié)果里面的順序會(huì)有所變化。主要原因還是采樣的時(shí)間太短,只有4.2秒,如果延長(zhǎng)profiling的時(shí)間,得到的結(jié)果會(huì)更加逼近真實(shí)值。
五.關(guān)于sprof:
sprof主要用于Gprof的補(bǔ)充,分析程序的共享庫(需要-g編譯)。一般的使用步驟:
1. export LD_PROFILE_OUTPUT=${PWD}
2. export LD_PROFILE=abc.so.A.B
3. export LD_LIBRARY_PATH=/path/to/lib/
4. 執(zhí)行使用該so的主程序
5. 執(zhí)行sprof so_file_name.so so_file_name.so.profile
注意:在實(shí)際執(zhí)行時(shí)發(fā)現(xiàn)LD_PROFILE指向的文件名后有可能需要加上實(shí)際的數(shù)字才可以。
在本次實(shí)驗(yàn)中,在生成profiling report的時(shí)候會(huì)發(fā)生錯(cuò)誤:
sprof libjpeg.so.9 libjpeg.so.9.profile
Inconsistency detected by ld.so: dl-open.c: 611: _dl_open: Assertion `_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT' failed!
按照Google Search Result的說法,在老版本的glibc里面會(huì)有這個(gè)問題,新版本有可能已經(jīng)解決了。不過因?yàn)閛profile完全可以profiling shared library,所以只是簡(jiǎn)單的嘗試了一下,沒有繼續(xù)深入研究這個(gè)話題。上面的經(jīng)驗(yàn)或許會(huì)對(duì)有興趣的開發(fā)者有所借鑒。
六.小結(jié):
盡管gprof有各種這樣那樣的限制和不足,如果能夠合理規(guī)避,對(duì)于代碼執(zhí)行時(shí)間大部分是在用戶空間的計(jì)算密集型的應(yīng)用程序,gprof還是非常方便好用的。
評(píng)論