2015年4月27日 星期一

[Embedded] 如何替換系統內預設的 memcpy 函數

最近使用 perf 替手邊的嵌入式系統進行效能分析,發現系統內最常被用到的函數是 memcpy(),若能夠優化此函數,應該可以改善系統效能吧。以下記錄整個實驗過程。

1. 系統效能分析
編譯perf的方法可參考此篇文章。使用 "perf top" 可以得知觀察系統當前的狀態,找到最耗時的函數。 
"perf top"原文說明如下:

The default sampling event is cycles and default order is descending number of samples per symbol, thus perf top shows the functions where most of the time is spent. By default, perf top operates in processor-wide mode, monitoring all online CPUs at both user and kernel levels. It is possible to monitor only a subset of the CPUS using the -C option.
下圖為"perf top"的執行結果,由圖中可得知 libc-2.10.1.so 內的 memcpy 函數是整個系統最耗時的部分,因此我將針對此函數進行分析,評估是否有優化的空間。

2. memcpy 函數分析
首先需要先檢視 libc-2.10.1對memcpy的實作方式,glibc 原始碼可至 http://ftp.gnu.org/gnu/glibc/ 下載。我分別下載了 libc-2.10.1 和 libc-2.21 的程式碼並進行比較。其主要差別在於libc-2.10.1使用C語言撰寫,而 libc-2.21將memcpy則會使用ARM的組合語言。 
  • 比較 glibc-2.10.1\string\memcpy.c 與 glibc-2.21\string\memcpy.c,其內容幾乎一樣。 
  • 但 glibc-2.21 針對 memcpy 新增了 ARM 的組語版本 (\glibc-2.21\sysdeps\arm\memcpy.S),當編譯ARM版本的 libc 時,便會選擇編譯組語版本的 memcpy。 
合理的推論,在 ARM 架構下, \arm\memcpy.S 的運作效能應該優於 \string\memcpy.c

3. 如何替換 libc 內的 memcpy
為了簡單,我並沒有使用 glibc-2.21 的 memcpy,我直接從 android source code中取出對應的 memcpy.S,用來練習置換 libc memcpy,原始程式可至 github 取得。 
方法一:直接 link 自行改寫的 memcpy library,用法如下:
$(CC) memcpy_test.o memcpy.o -o memcpy_test
$ arm-none-linux-gnueabi-readelf -s ./memcpy_test
  110: 00008520     0 FUNC    GLOBAL DEFAULT   12 memcpy
  125: 000083f0     0 FUNC    GLOBAL DEFAULT  UND malloc@@GLIBC_2.4 
方法二:透過設定 LD_PRELOAD 可切換動態連結時所尋找的 library 順序
$(CC) memcpy_test.o  -o memcpy_test
$ arm-none-linux-gnueabi-readelf -s ./memcpy_test
  104: 000083f8     0 FUNC    GLOBAL DEFAULT  UND memcpy@@GLIBC_2.4
  110: 00008404     0 FUNC    GLOBAL DEFAULT  UND malloc@@GLIBC_2.4 
$ export LD_PRELOAD=/tmp/test/mem_practice/libmymemcpy.so
$ ./memcpy_test  (使用自行編譯的 memcpy) 
$ export LD_PRELOAD=
$ ./memcpy_test  (使用 libc 的 memcpy)
可透過下列命令切換不同的 memcpy 並測試效能
$ export LD_PRELOAD=/tmp/test/mem_practice/libmymemcpy.so
$ ./perf bench mem all 
$ export LD_PRELOAD=
$ ./perf bench mem all 
4. 針對整個系統,更換 user space 所使用的 memcpy
若確定修改過後的 memcpy library 效能的確較佳,則可以修改 /etc/ld.so.preload,讓系統每次在作 dynamic link 時,總是先尋找自行撰寫的 memcpy library. 
ld.so.preload 的內容舉例如下:
/lib/libmymemcpy.so

參考資料
  1. http://stackoverflow.com/questions/27171485/various-glibc-and-linux-kernel-versions-compatibility
  2. http://stackoverflow.com/questions/9107259/how-to-replace-c-standard-library-functioin
  3. http://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick
  4. http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
  5. https://perf.wiki.kernel.org/index.php/Tutorial