# 2019q3 Homework3 (quiz3) contributed by < `Ting199708` > :::danger 注意細節!第一行的格式有明確規範 :notes: jserv ::: ## 第一題 :::info 考慮深度學習領域中,使用激勵函數 ReLU: $$ ReLU(x) = \begin{cases} x & \text{if } x \geq 0 \newline 0 & \text{if } x \lt 0 \end{cases} $$ RelU 計算量小,只要判斷輸入是否大於 `0`,沒有指數運算。下方程式 (`ReLU.c`) 是個常數時間的實作: ::: ### 程式碼 ```c float ReLU(float x) { union { float f; int32_t i; } out = {.f = x}; out.i = out.i OP1 ~(out.i >> V2); return out.f; } ``` ==分析與想法== #### union 首先,先來理解 union 是在定義什麼東西, 根據 [openhome.cc](https://openhome.cc/Gossip/CGossip/union.html), union 中所有資料成員共用一個空間,且同時間只能儲存其中一個成員的資料。 :::danger 儘量參照第一手材料,去翻閱 C 語言規格書!列出原文描述並解讀。 :notes: jserv ::: 另外,一個 union 配置的空間為最大長度的資料成員。 #### ReLU 根據 ReLU function 的定義,當 x<0 時, `ReLU(x) = 0` , x>=0 時, `ReLU(x) = x` ,因此在程式當中我們必須判斷 x 的正負號 我們利用電腦儲存有號數時的 sign bit ,透過將 `out.i >> 31` ,取得其 sign bit ,再反向即可得到以下結果 ```c if out.i<0 then ~(out.i >> 31) = 1111 1111 ... 1111 if out.i>=0 then ~(out.i >> 31) = 0000 0000 ... 0000 ``` ### 延伸問題 :::success 1. 解釋上述 ReLU 程式碼運作原理和應用場域 (歡迎寫篇深度學習的科普文章),並寫出另一個常數時間的實作 2. 研讀 [CMSIS-NN: Efficient Neural Network Kernels for Arm Cortex-M CPUs](https://arxiv.org/pdf/1801.06601.pdf),在 Arm Cortex-M 微處理器架構中,透過 SWAR (SIMD within a register) 的手法,可帶來 4 倍效能提升。嘗試解釋其原理 3. 透過 Intel SSE / AVX 等 SIMD 指令集,比照 [CMSIS-NN: Efficient Neural Network Kernels for Arm Cortex-M CPUs](https://arxiv.org/pdf/1801.06601.pdf),進行加速 ReLU 的實驗 ::: 1. ReLU 函數在 Machine Learning 中經常被用在 activation function ,因為其可將負數變為 0 的特性讓 AI 在 training 的過程中可以減少其線性的特性,增加 accuracy。 :::danger 這樣的描述對得起 AI 領域數十年的發展嗎? :notes: jserv ::: --- ## 第二題 :::info 在 8 位元 [Motorola 6809 處理器](https://en.wikipedia.org/wiki/Motorola_6809)上,有道指令叫做 [SEX](https://en.wikipedia.org/wiki/SEX_(computing)),寓意是 "Sign EXtend" `SEX 123` 應該輸出 `0`, 而 `SEX -3` 要輸出 `0xffffffff` (取決於有效位數) 考慮一個 32 位元版本的 `SEX` 實作如下,假設執行環境是 little-endian: ::: ### 程式碼 ```cpp #include <stdint.h> static inline uint32_t sex32(int32_t x) { union { TYPE w; struct { uint32_t lo, hi; }; /* FIXME: little-endian */ } z = {.w = x}; return z.hi; } ``` ==分析與想法== 這邊其實只要了解 union 的應用就可以知道答案,在題目中 union 定義為一 `type` 及 `struct {uint32_t lo, hi}` ,因此 type 型別的長度必須為 64 ,故應選擇 ==uint64_t== ### 延伸問題 :::success 1. 解釋程式運作原理,並改寫為適用於 big/little-endian 的程式碼 2. 找出 Sign Extend 的應用案例,特別是密碼學的應用 ::: 1. 當我們將一 32 bit 有號數放進 struct {uint32_t lo, hi} 中後,因為其符號延伸的特性,若該數小於零,則 `hi = 1111 1111 ... 1111` ,反之,則 `hi = 0000 0000 ... 0000`,因此程式只要 `return z.hi` 即可得到結果 若要將此程式改為 big/little-endian 適用的話,可以改為以下程式碼 ```c static inline uint32_t sex32(int32_t x) { int32_t a; union { uint64_t w; struct { uint32_t lo, hi; }; } z z.w = 1; if(z.hi == 0) { a = x; } else { a = bswap_32(x); } z.w = a; return z.hi; } ``` 其中 bswap_32 可將位元反轉,因此可將 big-endian 轉為 little-endian :::warning 改為不需要執行時期判斷的實作,也就是善用 C preprocessor :notes: jserv ::: --- ## 第三題 ### 題目 :::info 延伸測驗 `2` 的 `sex32`,用以改寫 [解讀計算機編碼](https://hackmd.io/@sysprog/rylUqXLsm) 提及的常數時間 `abs` 實作 (輸入是 32 位元整數),程式碼如下: ::: ### 程式碼 ```c static inline int abs_with_sex(int x) { return (x ^ sex32(x)) OP2 sex32(x); } ``` ==分析與想法== 這邊要透過 sex 來實作 abs ,首先,先看到題目已知的部分 ```c (x ^ sex32(x)) ``` 當 ==x < 0== 時, (x ^ sex32(x)) = ~x 當 ==x >= 0== 時, (x ^ sex32(x)) = x 此函式的輸出結果為 abs(x),所以 ==x < 0== 時 ```c (x ^ sex32(x)) OP2 sex32(x) = ~x OP2 (-1) = abs(x) ``` 因此, 根據二補數的定義, OP2 為 - 號 ==x >= 0== 時 ```c (x ^ sex32(x)) OP2 sex32(x) = x OP2 0 = abs(x) ``` 再次驗證了, OP2 為 - 號 --- ## 第四題 :::info 延伸 C 語言: goto 和流程控制,考慮以下 coroutine 實作程式碼: (coroutine.c) ::: ### 程式碼 ```c typedef enum ASYNC_EVT { ASYNC_INIT = 0, ASYNC_DONE = -1 } async; /* Declare the async state */ #define async_state unsigned int _async_kcont struct async { async_state; }; /* Mark the start of an async subroutine */ #define async_begin(k) \ switch ((k)->_async_kcont) { \ XXX1 case 0: XXX2 /* Mark the end of a generator thread */ #define async_end \ default: \ return ASYNC_DONE; \ } /* Wait until the condition succeeds */ #define await(cond) await_while(!(cond)) /* Wait while the condition succeeds */ #define await_while(cond) \ case __LINE__: \ if (cond) return __LINE__ \ /* Initialize a new async computation */ #define async_init(state) (state)->_async_kcont = ASYNC_INIT /* Check if async subroutine is done */ #define async_done(state) (state)->_async_kcont == ASYNC_DONE /* Resume a running async computation and check for completion */ #define async_call(f, state) \ (async_done(state) || ASYNC_DONE == ((state)->_async_kcont = (f)(state))) struct async_sem { unsigned int count; }; /** * Initialize a semaphore * * This macro initializes a semaphore with a value for the * counter. Internally, the semaphores use an "unsigned int" to * represent the counter, and therefore the "count" argument should be * within range of an unsigned int. * * \param s A pointer to the object representing the semaphore. * \param c (unsigned int) The initial count of the semaphore. */ #define init_sem(s, c) (s)->count = c /** * Wait for a semaphore * * This macro carries out the "wait" operation on the semaphore. The * wait operation causes the protothread to block while the counter is * zero. When the counter reaches a value larger than zero, the * protothread will continue. * * \param s A pointer to the object representing the semaphore. */ #define await_sem(s) \ do { \ await((s)->count > 0); \ --(s)->count; \ XXX3 } while (0) /** * Signal a semaphore * * This macro carries out the "signal" operation on the semaphore. The * signal operation increments the counter inside the semaphore, which * eventually will cause waiting protothreads to continue executing. * * \param s A pointer to the object representing the semaphore. */ #define signal_sem(s) ++(s)->count XXX4 /* Use case */ #include <stdio.h> #include <unistd.h> #define NUM_ITEMS 32 #define BUFSIZE 8 static int buffer[BUFSIZE]; static int bufptr; static void add_to_buffer(int item) { printf("Item %d added to buffer at place %d\n", item, bufptr); buffer[bufptr] = item; bufptr = (bufptr + 1) % BUFSIZE; } static int get_from_buffer(void) { int item = buffer[bufptr]; printf("Item %d retrieved from buffer at place %d\n", item, bufptr); bufptr = (bufptr + 1) % BUFSIZE; return item; } static int produce_item(void) { static int item = 0; printf("Item %d produced\n", item); return item++; } static void consume_item(int item) { printf("Item %d consumed\n", item); } static struct async_sem full, empty; static async producer(struct async *pt) { static int produced; async_begin(pt); for (produced = 0; produced < NUM_ITEMS; ++produced) { await_sem(&full); add_to_buffer(produce_item()); signal_sem(&empty); } async_end; } static async consumer(struct async *pt) { static int consumed; async_begin(pt); for (consumed = 0; consumed < NUM_ITEMS; ++consumed) { await_sem(&empty); consume_item(get_from_buffer()); signal_sem(&full); } async_end; } static async driver_thread(struct async *pt) { static struct async cr_producer, cr_consumer; async_begin(pt); init_sem(&empty, 0); init_sem(&full, BUFSIZE); async_init(&cr_producer); async_init(&cr_consumer); await(producer(&cr_producer) & consumer(&cr_consumer)); async_end; } int main(void) { struct async driver_pt; async_init(&driver_pt); while (!async_call(driver_thread, &driver_pt)) { usleep(10); /* why? */ } return 0; } ``` ### 延伸問題 :::success 1. 解釋上述程式運作原理,特別是和 Duff’s device 的關聯 2. 參照 ProtoThread 程式碼,比較上述差異,需要設計實驗,分析兩者在切換 coroutine 的執行時間和記憶體開銷成本 3. 整合 timeout 機制,允許 callback function 在指定的時間執行,如 TimerCallback callback function :::