# How C programs work (返回[2016年暑期系統軟體課程:台南場次](https://hackmd.io/EwZg7AbALAjArADgLSShJUBGECcSEAME6uYmB2OAplAGa1A)) ## 有所變,有所不變 * 1980 年代的電腦廣告: [IMSAI 8080](https://en.wikipedia.org/wiki/IMSAI_8080) ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_xDmCCv0k00K_p.299401_1446127674000_computer.jpg) * source: [twitter](https://twitter.com/HistoryInPics/status/552536619849613312) * USD $5995 (而且是 35 年前的物價!) 只能買到 8-bit 微處理器,搭配 64 KB 主記憶體和 10 MB 硬碟。有趣的是,當時軟體開發的模式部分還延續至今 * 硬體突飛猛進,軟體的問題依舊還在 ## Maslow’s pyramid of code review ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_xDmCCv0k00K_p.299401_1446087965957_code-revew.png) * 21 世紀的軟體開發均已規模化,絕非「有就好」,而是持續演化和重構,code review 是免不了的訓練 * uber 工程師 Charles-Axel Dein 認為好的程式碼應該要: * [ Correct ] : 做到預期的行為了嗎?能夠處理各式邊際狀況嗎?即便其他人修改程式碼後,主體的行為仍符合預期嗎? * [ Secure ] : 面對各式輸入條件或攻擊,程式仍可正確運作嗎? * [ Readable ] : 程式碼易於理解和維護嗎? * [ Elegant ] : 程式碼夠「美」嗎?可以簡潔又清晰地解決問題嗎? * [ Altruist ] : 除了滿足現有的狀況,軟體在日後能夠重用嗎?甚至能夠抽離一部分元件,給其他專案使用嗎? * 「需求」層次: 正確 → 安全 → 可讀 → 優雅 → 利他 * @**[徐元豪](https://www.facebook.com/profile.php?id=100000019155769&fref=ufi)** : 「工程師都會寫程式,但是 (往往) 欠了 code review 的能力。code review 不是用來戰別人,而是訓練自己如何看得懂別人程式、了解別人的想法,常看人家的程式,無形中是在訓練自己。」 * [@](https://twitter.com/Chaoint)Chaoint  : 「沒關係。」總是講給自己聽的。 * 「人們不敘述自己的過往,而是為自己的過往作證。」—— Frantz Fanon * 「如果你把游泳池當作浴缸泡著,再泡幾年還是不會游泳」 -- jserv 延伸閱讀: [Code Reading](https://en.wikipedia.org/wiki/Code_Reading) * 大量篇幅回顧 C 語言概念,以及在真實世界中的程式如何展現,細節! ## 什麼叫做簡潔? ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_xDmCCv0k00K_p.299401_1446123262319_math.jpg) 尤拉猜想是 Euler 在 1769 年對費馬定理的延伸: n 個正整數的 k 次方的總和,若是另一個正整數的 k 次方,則 n 不可能小於 k。n = 2 就是費馬最後定理,不過這個猜想在 1966 年被號稱「最短論文」給否定。 延伸閱讀:  * [美麗的錯誤--猜想的真與假](http://www.mathland.idv.tw/fun/erroneous.htm) * [邏輯思維:費馬大定理](https://www.youtube.com/watch?v=7nORW4edSaI) (YouTube) ## 「事實」很容易被遮蔽,所以我們要 Benchmark / Profiling ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_xDmCCv0k00K_p.299401_1446124062219_truth.jpg) source: [twitter](https://twitter.com/Harvey1966/status/624099087466500096) ## 《進擊的鼓手》之後 ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_xDmCCv0k00K_p.299401_1446103470188_pain.png) "No pain, no gain" source: [CSAIL at MIT twitter](https://twitter.com/MIT_CSAIL/status/614140774620332032/photo/1) * 延伸閱讀: [Operational Amplifier](http://memo.cgu.edu.tw/shin-yan/0909_Electronics(I)/2_OP.pdf) ## 理解系統程式 * 從實際案例著手: [從無到有打造類似 Facebook 社群網站](https://embedded2016.hackpad.com/MT7688-cgi-server-facebooc-EqEoM4o7nVv) **計算機結構的改變** [ [source](https://www.facebook.com/shihhaohung/posts/995419510500537) ] * 早年計算能力相對低的年代,常常有用查表法代替計算,有空間換取時間的做法,來增進效能。... 那個時候 個人電腦的 CPU 可以在一個時脈週期中讀取一筆資料,但是要做乘法計算則需要幾十個時脈週期,所以用查表的比較快。除法和超越函數更是如此,而現在還有一些低階的處理器,還在用這些技巧。 * 後來當 CPU 時脈提高,但記憶體存取相對變慢的時候,我們必須反過來減少記憶體存取的次數,所以高階處理器 cache 越來越大,做 data  prefetch 來提早取得資料、使用 multi-threaded  architecture 來容忍資料遲到的狀況、使用壓縮的方式傳送資料,甚至還會用 speculation 的方式來猜測資料是在哪裡和是什麼。 * 在多處理機和多核心電腦上,存取資料的問題更嚴重,除了時間延遲和頻寬之外,還要考慮到尖峰時刻塞車的問題,所以有時候簡單的工作,就可能就不分工了,要不就由一個 CPU 代表去做,做完把結果給大家,要不就大家都做同樣的事情。前者多半在有共享記憶體的多核心處理器上看到,後者多半在分散式的系統看到。 * 到了異質計算的年代,CPU 和 GPU 的分工,更需要好好地做效能分析。因為傳統 GPU 和 CPU 不共享記憶體,透過較慢的 PCIe  Bus 交換資料,所以有些工作 CPU 自己做比較快。另一方面,當 GPU 有超過 2000 個核心的時候,用重複的計算 (redundant  computation)取代資料交換,也是常見的事。 * [](http://www.hsafoundation.com/)[http://www.hsafoundation.com/](http://www.hsafoundation.com/) * 更進一步談巨量資料,為了節省資料的取得時間,我們往往費盡心思。我們花時間將資料和計算擺在同一個地方,做所謂的 data computation  co-location,將重複出現的資料利用 data  deduplication 技術節省儲存空間和取得時間,用一堆目錄 (indexing) 讓資料可以快速被找到。 * 當計算機結構有所不同時,優化的策略可能會隨之而變,不能食古不化。但原理雖然簡單,系統和實作技巧卻越來越複雜,軟硬體優化的機會越來越多,可惜能夠真正連通理論和實務的人則越來越少。 * 以上這些技術,講起來很容易,但在實作上,必須先搞清楚運算和資料的相對位置、距離、時間先後、相依性、數量等等,才知道該如何取捨。但很多人根本不會用效能分析工具,就在那邊瞎子摸象,隨便亂講,這時候要解決問題,就需要瞎貓遇到死耗子的運氣。 * i586 和 i686 看起來指令相似,但本質不同! * 從 i686 (Pentium Pro) 開始,底層已經是** RISC 架構** * 因為現在計算機結構改變很大 * 即便把程式用組合語言重寫,效能也不見得比 Compiler 產生的還好 * 效能的問題在存取資料本身 * 組合語言會快,是因為你分析過程式要怎樣寫才可以比較快 * 直接照著程式碼的邏輯改寫組合語言不見得比較好 * [hyperloglog](https://en.wikipedia.org/wiki/HyperLogLog) * 使用 1.5k 表達 10 億筆資料 * <u>正規表示法? </u> * [Python implementation of Hyperloglog, redis, fuzzy hashing for malware detection](https://bigsnarf.wordpress.com/2013/03/16/python-implementation-of-hyperloglog-redis-fuzzy-hashing-for-malware-detection/)  ( 從測驗來刺激思考: Quiz-0 ) 「[你所不知道的 C 語言](http://hackfoldr.org/dykc/)」系列講座 [A circuit-like notation for lambda calculus](http://csvoss.github.io/projects/2015/11/08/lambda-circuitry.html) * 1928年,[Alonzo Church](https://en.wikipedia.org/wiki/Alonzo_Church) 發明了lambda演算(當時他 25 歲)。Lambda 演算被設計為一個通用的計算模型,並不是為了解決某個特定的問題而誕生的。1929 年,Church 成為普林斯頓大學教授。1932 年,Church 在 Annals of Mathematics 發表了一篇論文,糾正邏輯領域裡幾個常見的問題,他的論述中用了lambda演算。1935年,Church 發表論文,使用 lambda 演算證明基本數論中存在不可解決的問題。1936 年 4 月,Church發 表了一篇兩頁紙的 “note”,指出自己 1935 年那篇論文可以推論得出,著名的Hilbert“可判定性問題”是不可解決的 ## Programming Small * 在小處下功夫,不放棄整體改善的機會 * [快速計算和表達圓周率](http://chamberplus.myweb.hinet.net/ems_2.htm) * [解說](http://godspeedlee.myweb.hinet.net/trick.html) * [字串反轉](http://godspeedlee.myweb.hinet.net/c_str/c_str.htm) * C 語言講究效率 * 為什麼 C 語言沒有內建 swap 機制? * 很難作出通用且有效率的 swap 方式 * [Swapping in C, C++, and Java](http://www.cs.utsa.edu/~wagner/CS2213/swap/swap.html)  * 最佳化來自對系統的認知 * 假設我們有兩個****有號整數***: ```C= #include<stdint.h> int32_t a, b; ``` * 然後原本涉及到分支的陳述: **if (b < 0) a++;** * 可更換為沒有分支的版本: # superscalar [](https://en.wikipedia.org/wiki/Superscalar_processor)https://en.wikipedia.org/wiki/Superscalar_processor **a -= b >> 31;** * 為什麼呢?一旦 b 右移 31 個 bit,在右移 `>>` 時在前面補上 sign bit ,所以移完會是 0xffff 也就是-1,故結果會是 `a -= -1`,即 `a++`,於是,原本為負數的值就變成 1 ,而原本正數的值就變成 0,又因為原本的行為是有條件的 a++,如此就是數值操作,沒有任何分支指令。 ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_KqSNM2C0g5S_p.537916_1468510355863_undefined) ## GNU Toolchain * gcc : GNU compiler collection * as : GNU assembler * ld : GNU linker * gdb : GNU debugger 介紹完編譯工具後,來講點大概編譯的流程還有格式 **Compile flow ** 先來看這張圖: ![](http://i.imgur.com/9c0k1v0.png) .c 和 .s 我想大家比較常見,所以就解釋一下 .coff 和 .elf 是什麼: * [COFF (common object file format)](https://en.wikipedia.org/wiki/COFF) : 是種用於執行檔、目的碼、共享函式庫 (shared library)  的檔案格式 * [ELF (extended linker format)](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) : 現在最常用的文件格式,是一種用於執行檔、目的碼、共享函式庫和核心的標準檔案格式,用來取代COFF **GAS program format (AT&T)** ```asm= * .file “test.s” * .text * .global main * .type main, %function * main: * MOV R0, #100 * ADD R0, R0, R0 * SWI #11 * .end ``` 由一個簡短的 code 來介紹,在程式的section 會看到 `.` ,是定義一些 control information,如 _.file_ , _.global_ 等 * %function : 是一種 type 的表示方式 .type 後面可以放 function 或者是 object 來定義之 * SWI #11 : 透過 software interrupt 去中斷現有程式的執行,通常會存取作業系統核心的服務 (system call) * .end : 表示是 the end of the program 注意,在後來的 ARM 中,一律以 "SVC" (Supervisor Call) 取代 "SWI",但指令編碼完全一致 ==> [ARM Instruction Set Quick Reference Card](http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001l/QRC0001_UAL.pdf) 以下簡介 ELF 中個別 section 的意義: (注意: ELF section 的命名都由 `.` 開頭)  * .bss : 表示未初始化的 data,依照定義,系統會賦予這些未初始化的值 0 * .data : 表示有初始過的 data  * .dynamic : 表示 dynamic linking information * .text : 表示 "text" 或者是 executable instructions **寫程式的要點** * 程式是拿來實現一些演算法 (想法) 進而去解決問題的 * 可以透過 Flow of control 去實現我們的程式 * Sequence * Decision: if-t * hen-else, switch * Iteration: repeat-until, do-while, for * 將問題拆成多個更小而且方便管理的單元 (每一個單元或稱 function ,盡量要互相獨立) * Think: In C, for / while ? **Procedures** 來複習一下名詞 * Arguments: expressions passed into a function * Parameters: values received by the function * Caller & callee [呼叫者(通常就是在其他函式呼叫的function) 與 被呼叫者(被呼叫的 function)] ==> "argument" 和 "parameter" 在中文翻譯一般寫「參數」或「引數」,常常混淆 ==> "argument" 的重點是「傳遞給函式的形式」,所以在 C 語言程式寫 `int main(int argc, char *argv[])` 時,我們稱 argc 是 argument count,而 argv 是 argument vector ==> "parameter" 的重點是「接受到的數值」,比方說 C++ 有 [parameterized type](https://isocpp.org/wiki/faq/templates#param-types),就是說某個型態可以當作另外一個型態的「參數」,換個角度說,「型態」變成像是數值一樣的參數了。 ==> [](https://en.wikipedia.org/wiki/Parameter_(computer_programming))[https://en.wikipedia.org/wiki/Parameter_(computer_programming](https://en.wikipedia.org/wiki/Parameter_(computer_programming)) ![](http://i.imgur.com/K2XsAUi.png) 在撰寫程式常常會使用呼叫( call ),在上圖中高階語言直接將參數傳入即可,那麼在組語的時候是如何實作的呢?是透過暫存器? Stack ?  memory ? In what order ?我們必須要有個 protocol 來規範 * ABI  [](https://en.wikipedia.org/wiki/Application_binary_interface)https://en.wikipedia.org/wiki/Application_binary_interface **ARM Procedure Call Standard (AAPCS)** * ARM Ltd. 定義一套規則給 procedure entry 和 exit * Object codes generated by different compilers can be linked together * Procedures can be called between high-level languages and assembly * AAPCS 是 [Procedure Call Standard for the ARM® Architecture](http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf) 的簡稱,從 10 年前開始,全面切換到 EABI (embedded ABI) * 過去的 ARM ABI 稱為 oabi (old ABI),閱讀簡體中文書籍時,要格外小心,因為資訊過時  * APCS 定義了 * Use of registers * Use of stack * stack-based 資料結構型式 * argument passing 的機制 * first four word arguments 傳到 R0 到 R3 * 剩餘的 parameters 會被 push 到 stack (參數依照反過來的排序丟入堆疊中) * 少於 4 個 parameters 的 procedure 會比較有效率 ![](http://i.imgur.com/0TrhpPW.png) * Return value * one word value 存在 R0 * 2 ~ 4 words 的 value 存在 ( R0-R1, R0-R2, R0-R3) 以下測試一個參數數量為 4 和 5的程式: ```C= * int add4(int a, int b, int c , int d){ * return a + b + c + d; * } * int add5(int a, int b, int c , int d, int e){ * return a + b + c + d + e; * } ``` 程式編譯後,用 objdump 組譯會得到類似以下: ![](http://i.imgur.com/dpdfdyU.png) 紅框標注的是比左邊多出的程式碼,從這裡可以看到參數 1-4 是存在 R0-R3,而第 5個參數存在原本 sp + 4  的位置,隨著程式碼進行 R0-R3 存在 stack 中,圖下為 stack 恢復前的樣子: ![](http://i.imgur.com/5zKhm0D.png) 因此若寫到需要輸入5個或以上的參數時,就必須存取外部記憶體,這也導致效能的損失。 ==> xorg xserver 的最佳化案例 **Standard ARM C program address space** 下圖為 ARM C program 標準配置記憶體空間的概念圖: ![](http://i.imgur.com/KooHIPv.png) **Accessing operands** 通常 procedure 存取 operands 透過以下幾種方式: * An argument passed on a register : 直接使用暫存器 * An argument passed on the stack : 使用 stack pointer (R13) 的相對定址 (immediate offset) * A constant : PC-relative addressing * A local variable : 分配在 stack 上,透過 stack pointer 相對定址方式存取 * A global variable : 分配在 static area (就是樓上圖片的 static data),透過 static base (R9) 相對定址存取 用幾張圖來表現出存取 operands 時,stack 的變化: 圖下為 passed on a register: ![](http://i.imgur.com/snkxkAH.png) 圖下為存取 local variables: ![](http://i.imgur.com/kR6PYXP.png) ## Target Triple 在使用 Cross Compiler 時,gcc 前面總有一串術語,例如: * arm-none-linux-gnueabi-gcc 這樣 `arm-none-linux-gnueabi-`稱為 target triple,通常規則如下: <target>[<endian>][-<vender>]-<os>[-<extra-info>] * vendor 部份只是常見是可以塞 vendor 而已, 但沒有一定塞 vendor 的資訊 * extra-info 部份大部份是在拿來描述用的 ABI 或著是 library 相關資訊 先以常見 x86 的平台為例子: gcc  下 -v 可以看到以下: ```Target: x86_64-redhat-linux``` <target>-<vendor>-<os> 的形式 Android Toolchain: ```Target: arm-linux-androideabi``` <target>-<os>-<extra-info> androideabi : 雖然 Android 本身是 Linux 但其 ABI 細節跟一般 linux 不太一樣 Linaro ELF toolchain: ```Target: arm-none-eabi``` <target>-<vender>-<extra-info> vender = none extra-info = eabi Linaro Linux toolchain: ```Target: arm-linux-gnueabihf``` <target><endian>-<os>-<extra-info> extra-info: eabi: EABI hf : Hard float, 預設有 FPU * soft (GPR), softfp (GPR -> float register), hardfp Linaro big-endian Linux toolchain: ```Target: armeb-linux-gnueabihf``` <target><endian>-<vender>-<extra-info> endian = be = big-endian Buildroot 2015-02 ```Target: arm-buildroot-linux-uclibcgnueabi``` <target>-<vender>-<os>-<extra-info> extra-info: uclibc 用 uclibc (通常預設是 glibc, uclibc 有些細節與 glibc 不同) gnu : 無意義 eabi : 採用 EABI NDS32 Toolchain: ```Target: nds32le-elf``` <target><endian>-<os> Andes 家的,應該淺顯易懂不用解釋 由以上眾多 pattern 大概可以歸納出一些事情: vender 欄位變化很大,os 欄位可不填。若不填的話,通常代表 Non-OS (Bare-metal) source: [](http://kitoslab.blogspot.tw/2015/08/target-triple.html)[http://kitoslab.blogspot.tw/2015/08/target-triple.html](http://kitoslab.blogspot.tw/2015/08/target-triple.html) ## 編譯器原理 為何我們要理解編譯器?這是電腦科學最早的思想體系 * [Compiler的多元應用](https://embedded2015.hackpad.com/Compiler-rtfoECtyuOd) * 從理解動態編譯器 (如 Just-in-Time) 的運作,可一路探索作業系統核心, ABI,效能分析的原理 * [編譯器和最佳化原理篇](https://embedded2015.hackpad.com/C-DWAUb7NnQF0) * 以 GNU Toolchain 為探討對象,簡述[編譯器如何運作,以及如何實現最佳化](http://www.slideshare.net/jserv/how-a-compiler-works-gnu-toolchain) * C 語言程式如何轉換為機械碼,以及最佳化的空間和限制 * [Compiler Development](http://slide.logan.tw/compiler-intro/) GCC 會透過 builtins 來達到最佳化,在 GCC flag -funit-at-a-time 指定時 (-O3 implies),原本的: * **sprintf(buf,"%s",str);**  在 front-end 會被改寫為: * **strcpy(buf,str);**  printf 本身就是一個小型的 interpreter,所以 GCC 的作法就是簡化複雜度,這對一般情況下是 正面的,不過涉及 function rewriting 的議題,對系統程式或設計一個作業系統核心來說,就是一個潛在的問題。 [ [Interpreter, Compiler, JIT from scratch](http://www.slideshare.net/jserv/jit-compiler) ] * [碎形程式 in C](http://blog.linux.org.tw/~jserv/archives/2011/09/_mandelbrot_set.html) (Brainf*ck 的版本作為 benchmark) [ [Virtual Machine Constructions for Dummies](http://www.slideshare.net/jserv/vm-construct) ] [ [A brief history of just-in-time](http://dl.acm.org/citation.cfm?id=857077) ] [ [Making an Embedded DBMS JIT-friendly](http://arxiv.org/abs/1512.03207) ] 值得注意的是,program loader 也很關鍵 * [PokémonGo Hacking without Jailbreak](https://docs.google.com/presentation/d/1XqU2GPG6l5nF8rsGTGbddQXk8k--5ZCDP7HlPC2D2eQ/edit#slide=id.g35f391192_00) ## 作業系統的核心裡面也有編譯器 * [A JIT for packet filters](https://lwn.net/Articles/437981/): Linux 核心已收錄 [Berkeley Packet Filter](https://secure.wikimedia.org/wikipedia/en/wiki/Berkeley_Packet_Filter) (BPF) JITC * 形式來說來,bpf 就是 in-kernel virtual machine * [BPF - the forgotten bytecode](https://blog.cloudflare.com/bpf-the-forgotten-bytecode/) * tcpdump 不但可分析封包的流向,連封包的內容也可以進行「監聽」 * 鳥哥 Linux 私房菜: [第五章、 Linux 常用網路指令](http://linux.vbird.org/linux_server/0140networkcommand.php) * [Berkeley Packet Filter (BPF)](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter) 會透過 bytecode 和 JIT compiler 來執行 ```C= * $ sudo tcpdump -p -ni eth0 -d "ip and udp" * (000) ldh      [12] * (001) jeq      #0x800           jt 2    jf 5 * (002) ldb      [23] * (003) jeq      #0x11            jt 4    jf 5 * (004) ret      #65535 * (005) ret      #0 ``` * [Introduction to Linux kernel tracing and eBPF integration](http://www.slideshare.net/vh21/meet-cutebetweenebpfandtracing) * [BPF Compiler Collection (BCC)](https://github.com/iovisor/bcc) * BCC is a toolkit for creating efficient kernel tracing and manipulation programs, and includes several useful tools and examples. It makes use of eBPF (Extended Berkeley Packet Filters), a new feature that was first added to Linux 3.15. ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_P9uuElzuDRa_p.537916_1464323384025_undefined) ## 實做案例 * [jit-construct](https://github.com/embedded2015/jit-construct) : Brainf*ck * [rubi](https://github.com/embedded2015/rubi) : Ruby-like JIT compiler * 將 [Rubi](https://github.com/embedded2015/rubi) 實做切換到 DynASM,並且設計效能評估機制,從而改善 * 原本 x86 code generator 換成 [DynASM 語法](http://luajit.org/dynasm.html) * [The Unofficial DynASM Documentation](https://corsix.github.io/dynasm-doc/) * 「[作業區](https://embedded2015.hackpad.com/2015q3-Homework-4-8AvSmXDYC38)」(C) * [amacc](https://github.com/jserv/amacc): Small C Compiler generating ELF executable for ARM architecture  ## Can we make Apache Spark 10x faster? 好比在金庸《射雕英雄傳》,馬鈺教郭靖修煉內功的方式,無外乎就是一些呼吸、走路、睡覺的法子。 ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CjXal2y5qT8_p.537916_1464071568240_undefined) (圖片來源:獵人 Hunter x Hunter) * [Apache Spark as a Compiler: Joining a Billion Rows per Second on a Laptop](https://databricks.com/blog/2016/05/23/apache-spark-as-a-compiler-joining-a-billion-rows-per-second-on-a-laptop.html) [ 舊有的資料處理模式 ] ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CjXal2y5qT8_p.537916_1464071670680_undefined) ```java= * class Filter(child: Operator, predicate: (Row => Boolean)) * extends Operator { * def next(): Row = { * var current = child.next() * while (current == null || predicate(current)) { * current = child.next() * } * return current * } * } ``` [ 以簡馭繁 ] ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CjXal2y5qT8_p.537916_1464071819148_undefined) ```java= * var count = 0 * for (ss_item_sk in store_sales) { * if (ss_item_sk == 1000) { * count += 1 * } * } ``` [ 進一步改善 ] * No virtual function dispatches * Intermediate data in memory vs CPU registers * Loop unrolling and SIMD * [生日悖論](https://zh.wikipedia.org/zh-hant/%E7%94%9F%E6%97%A5%E5%95%8F%E9%A1%8C) ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CMFMvFApyKC_p.537916_1464685464739_undefined) ## 編譯器技術對於現行運算需求的重要性 * [AutoFDO](https://gcc.gnu.org/wiki/cauldron2013?action=AttachFile&do=get&target=AutoFDO.pdf), Google * [GCC Function Instrumentation](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html) * 該機制出現於 GCC 2.x,由 Cygnus (現為 RedHat) 所提出,在 GCC 中對應的選項為:"finstrument-functions" * 測試 GCC Function Instrumentation 的小程式: (hello.c) ```C= * #include <stdio.h> * #define DUMP(func, call) \ * printf("%s: func = %p, called by = %p\n", __FUNCTION__, func, call) * void __attribute__((__no_instrument_function__)) * __cyg_profile_func_enter(void *this_func, void *call_site) * { DUMP(this_func, call_site); } * void __attribute__((__no_instrument_function__)) * __cyg_profile_func_exit(void *this_func, void *call_site) * { DUMP(this_func, call_site); } * main() { puts("Hello World!"); return 0; } ``` * 編譯與執行: ``` * $ **gcc -finstrument-functions hello.c -o hello** * $ **./hello** * __cyg_profile_func_enter: func = 0x8048468, called by = 0xb7e36ebc * Hello World! * __cyg_profile_func_exit: func = 0x8048468, called by = 0xb7e36ebc ``` * 看到 "__attribute__" 就知道一定是 GNU extension,而前述的 man page 也提到 -finstrument-functions 會在每次進入與退出函式前呼叫 "__cyg_profile_func_enter" 與 "__cyg_profile_func_exit" 這兩個 hook function。等等,「進入」與「退出」是又何解? * C Programming Language 最經典之處在於,雖然沒有定義語言實做的方式,但實際上 function call 皆以 stack frame 的形式存在。 * 如果我們不透過 GCC 內建函式 "__builtin_return_address" 取得 caller 與 callee 相關的動態位址,那麼仍可透過 -finstrument-functions,讓 GCC 合成相關的處理指令,讓我們得以追蹤。而看到 __cyg 開頭的函式,就知道是來自 Cygnus 的貢獻,在 gcc 2.x 內部設計可瞥見不少。 * 當我們試著移除 "__attribute__((__no_instrument_function__))" 那兩行來看看: (wrong.c) * 編譯與執行: ``` * $ **gcc -g -finstrument-functions wrong.c -o wrong** * $ **./wrong** * Segmentation Fault ``` * 發生什麼事情呢?請出 gdb 協助: ``` * $ **gdb -q ./wrong** * (gdb) **run** * Starting program: /home/jserv/HelloWorld/helloworld/instrument/wrong  * Program received signal SIGSEGV, Segmentation fault. * 0x0804841d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * 7        { * (gdb) **bt** * #0  0x0804841d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * #1  0x0804842d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * #2  0x0804842d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * ... * #30 0x0804842d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * #31 0x0804842d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 * #32 0x0804842d in __cyg_profile_func_enter (this_func=0x8048414, call_site=0x804842d) at wrong.c:7 ``` * 既然 "__cyg_profile_func_enter" 是 function hook,則本身不得也被施以 "function instrument",否則就無止盡遞迴了,不過我們也可以發現一件有趣的事情: ``` * $ **nm wrong | grep 8048414** * 08048414 T __cyg_profile_func_enter ``` * 如我們所見,"__cyg_profile_func_enter" 的位址被不斷代入 __cyg_profile_func_enter function arg 中。GNU binutils 裡面有個小工具 addr2line,我們可以該工具取得虛擬位址對應的行號或符號: ``` * $ **addr2line -e wrong 0x8048414** * /home/jserv/HelloWorld/helloworld/instrument/wrong.c:7 ``` * 就 Linux 應用程式開發來說,我們可透過這個機制作以下應用: * 撰寫特製的 profiler * 取得執行時期的 Call Graph * 置入自製的 signal handler,實做 backtrace 功能 * 模擬 Reflection 機制 * [Java reflection 參考](http://jjhou.boolan.com/javatwo-2004-reflection.pdf) ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CMFMvFApyKC_p.537916_1464672296022_undefined) 但 profiling 的成本在許多環境,像是雲端運算來說,實在太沈重,所以 Google 提出透過 perf 工具來取得 profiling 資料的 AutoFDO: ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CMFMvFApyKC_p.537916_1464672873563_undefined) * [Accelerating the Core of the Cloud](http://events.linuxfoundation.org/sites/events/files/slides/Accelerating%20the%20Core%20of%20the%20Cloud%20LCNA%20davest.pdf), Intel * Core software optimization: a strategy * Case Study: OpenStack ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CMFMvFApyKC_p.537916_1464673334453_undefined) ![](https://hackpad-attachments.s3.amazonaws.com/embedded2016.hackpad.com_CMFMvFApyKC_p.537916_1464673481784_undefined) * PLT vs. GOT * [](http://refspecs.linuxfoundation.org/ELF/zSeries/lzsabi0_zSeries/x2251.html)[http://refspecs.linuxfoundation.org/ELF/zSeries/lzsabi0_zSeries/x2251.html](http://refspecs.linuxfoundation.org/ELF/zSeries/lzsabi0_zSeries/x2251.html) [Skymizer](https://www.facebook.com/skymizer/) 針對日前釋出的 GCC 6.1,翻譯部份 [GCC Release note](https://gcc.gnu.org/gcc-6/changes.html): * 上半段: [](http://blog.skymizer.com/gcc_6_1/)[http://blog.skymizer.com/gcc_6_1/](http://blog.skymizer.com/gcc_6_1/) * 下半段: [](http://blog.skymizer.com/gcc_6_1_2/)[http://blog.skymizer.com/gcc_6_1_2/](http://blog.skymizer.com/gcc_6_1_2/) "so you all hate on C++. all right. all right. tell me. does your favorite language have a concept of friendship, then? eh? thought so." from [twitter](https://twitter.com/whitequark/status/756614865364525056) "C++ Is my favorite garbage collected language because it generates so little garbage." - Bjarne Stroustrup ([origin](http://www.stroustrup.com/bs_faq.html#really-say-that)) ## 隨堂測驗 - [ ]Quiz-0 : Give you the prototype of the function:  int p(int i, int N); use it to print the numbers from i up to N and then down to i again, one number per line Don’t use those keywords: do, while, for, switch, case, break, continue, goto, if. Don’t use ’:?’ You can only use ONE statement! ==> ```C= int p(int i, int N) { return (i < N && printf("%d\n", i) && !p(i + 1, N)) || printf("%d\n", i); } ``` - [ ]Quiz-1 : 只能使用位元運算子和遞迴,在C程式中實做兩個整數的加法  void add(int a, int b) { ... } => ```C= int add(int a, int b) { if (b == 0) return a; int sum = a ^ b; // 相加但不進位 int carry = (a & b) << 1; // 進位但不相加 return add(sum, carry); } ``` Reference: [](http://stackoverflow.com/questions/14695051/how-to-simulate-a-4-bit-binary-adder-in-c)http://stackoverflow.com/questions/14695051/how-to-simulate-a-4-bit-binary-adder-in-c - [ ]Quiz-2 : 給定一個 singly-linked list 結構: ```C= * typedef struct _List { * struct _List *next; * int val; * } List; ``` 實做得以交換存在 list 中兩個節點的函式: (考慮 HEAD 與 TAIL) int swap_list(List **head, List *a, List *b) { ... } ==> [](https://github.com/shengwen1997/sys2016_homework/blob/master/linked_list_swap/main.c)https://github.com/shengwen1997/sys2016_homework/blob/master/linked_list_swap/main.c