# quiz2 測驗1 EWMA問題(第2版) ###### tags: `linux2022` ## [測驗 `1`](https://hackmd.io/@sysprog/linux2022-quiz2#%E6%B8%AC%E9%A9%97-1) :::success 延伸問題: 3. 研讀 Linux 核心原始程式碼 [include/linux/average.h](https://github.com/torvalds/linux/blob/master/include/linux/average.h),探討其 [Exponentially weighted moving average](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average) (EWMA) 實作,以及在 Linux 核心的應用 ::: `#define DECLARE_EWMA(name, _precision, _weight_rcp)` 其中3個輸入參數定義 * `name`: 表示整個 macro 定義出來的一組 macro 以及 struct 的名稱。 * `_percision`: 表示用多少個 bits 來表示內部儲存的小數部分。 * `_weight_rcp`: 表示權重 $\alpha$ 的倒數, $\alpha=1 / weight\_rcp$。 ## EWMA 數學式推導 $S_t = \begin{cases} Y_0, & t = 0 \\ \alpha Y_t + (1 - \alpha) \cdot S_{t-1}, & t > 0 \end{cases}$ $\begin{aligned} S[N]&=\alpha Y[N]+(1-\alpha)S[N-1]\\ &=\alpha \{Y[N]+ ({1\over\alpha} - 1)S[N - 1]\}\\ &=\alpha\{Y[N]+{1\over\alpha}S[N-1]-S[N-1]\} \end{aligned}$ 在 [include/linux/average.h](https://github.com/torvalds/linux/blob/master/include/linux/average.h) 中對應的程式段落為 ```c static inline void ewma_##name##_add(struct ewma_##name *e, \ unsigned long val) \ { WRITE_ONCE(e->internal, internal ? \ (((internal << weight_rcp) - internal) + \ (val << precision)) >> weight_rcp : \ (val << precision)); \ } ``` 裡面的 ```c WRITE_ONCE(e->internal, internal ? \ (((internal << weight_rcp) - internal) + \ (val << precision)) >> weight_rcp : \ (val << precision)); ``` 即為對應的EWMA數學式運算 $\begin{aligned} S[N]=\alpha\{Y[N]+{1\over\alpha}S[N-1]-S[N-1]\} \end{aligned}$ 其中 ```c internal ? (((internal << weight_rcp) - internal) + (val << precision)) >> weight_rcp : (val << precision)); ``` 為[條件運算子](http://kaiching.org/pydoing/c/c-operator.html) > `expr1?expr2:expr3` > 運算式 **expr1 為真** ==>運算結果會是**運算式 expr2** > 運算式 **expr1 為假** ==>運算結果則是**運算式 expr3** 對應到我們的程式相當於 ``` internal ? expr2:expr3 ``` internal有值==> 做運算式expr2 internal為0==> 做運算式expr3 :::info 因此相當於 `WRITE_ONCE(e->internal,第二個變數);` **第二個變數**根據**internal值條件**,切換代入 ``` if(internal) //internal有值時 { 第二個變數代入值為 (((internal << weight_rcp) - internal) + (val << precision)) >> weight_rcp; }else{ 第二個變數代入值為 (val << precision); } ``` `(((internal << weight_rcp) - internal) + (val << precision)) >> weight_rcp;` 和 $\begin{aligned} S[N]=\alpha\{Y[N]+{1\over\alpha}S[N-1]-S[N-1]\} \end{aligned}$ 對比 1. ``` ((internal << weight_rcp) - internal) ``` 為$\begin{aligned} {1\over\alpha}S[N-1]-S[N-1] \end{aligned}$ 因為$\alpha$小於1,所以**乘$1\over\alpha$,相當於左移<< weight_rcp** 2. ``` (val << precision) ``` 為$\begin{aligned} Y[N] \end{aligned}$ 3. ``` >> weight_rcp ``` 為乘 $\alpha$ 倍 因為$\alpha$小於1,所以**乘$\alpha$,相當於右移>> weight_rcp** ::: ## [include/linux/average.h](https://github.com/torvalds/linux/blob/master/include/linux/average.h) 其他內容 引用 [meyr 同學](https://hackmd.io/@meyr543/B1lLmZV-5#3Exponentially-weighted-moving-average-EWMA) 介紹 > #### 名詞解釋 > > > | Funciton Name | Description | > | ---------------------- | -------------------------- | > | **BUILD_BUG_ON(cond)** | 停止compile如果cond是true. | > |**BUILD_BUG_ON_NOT_POWER_OF_2(var)** | 停止compile如果var不是2的次方。 > |**__builtin_constant_p(var)** | 判斷var是否為常數,返回1為常數,返回0則否。 > |**READ_ONCE/WRITE_ONCE** | 參考[這裡](/YcjVoBx5R_mnKxX9-H6pEg#READ_ONCEWRITE_ONCE)</br>就是volatile的封裝。當需要用的時候把變數轉換成volatile型態,編譯器只是保證 2 個 volatile 變數之間讀取不會被亂序。 ## READ_ONCE/WRITE_ONCE 介紹 [WRITE_ONCE READ_ONCE 函數的介紹與使用](https://blog.csdn.net/Adrian503/article/details/106921769) > 為什麼要用READ_ONCE()和WRITE_ONCE()這兩個宏呢?這裡起到關鍵作用的就是volatile ,它主要告訴編譯器: > > 1、聲明這個變量很重要,不要把它當成一個普通的變量,做出錯誤的優化。 > > 2、保證CPU 每次都從內存重新讀取變量的值,而不是用寄存器中暫存的值。 > > 因為在 多線程/多核 環境中,不會被當前線程修改的變量,可能會被其他的線程修改,從內存讀才可靠。 > > 還有一部分原因是,這兩個宏可以作為標記,提醒編程人員這裡面是一個多核/多線程共享的變量,必要的時候應該加互斥鎖來保護。 [Linux内核中的READ_ONCE和WRITE_ONCE宏](https://zhuanlan.zhihu.com/p/344256943) [Linux内核中的READ_ONCE和WRITE_ONCE宏2](https://blog.csdn.net/Roland_Sun/article/details/107365134) ## EWMA 實作,在 Linux 核心的應用 依照 `DECLARE_EWMA()` 定義 > `#define DECLARE_EWMA(name, _precision, _weight_rcp)` > 其中3個輸入參數定義 > * `name`: 表示整個 macro 定義出來的一組 macro 以及 struct 的名稱。 > * `_percision`: 表示用多少個 bits 來表示內部儲存的小數部分。 > * `_weight_rcp`: 表示權重 $\alpha$ 的倒數, $\alpha=1 / weight\_rcp$。 [drivers/net/wireless/mediatek/mt76/mt76x02_mac.h](https://github.com/torvalds/linux/blob/dbe69e43372212527abf48609aba7fc39a6daa27/drivers/net/wireless/mediatek/mt76/mt76x02_mac.h) ```c=34 DECLARE_EWMA(pktlen, 8, 8); struct mt76x02_sta { struct mt76_wcid wcid; /* must be first */ struct mt76x02_vif *vif; struct mt76x02_tx_status status; int n_frames; struct ewma_pktlen pktlen; }; ``` `DECLARE_EWMA(pktlen, 8, 8);` 代表 * `name`: pktlen * `_percision`: 表示**用 8 bits** 來表示內部儲存的**小數部分。** * `_weight_rcp`: 表示權重 $\alpha$ 的倒數, $\alpha=1 / weight\_rcp=1 / 8$。 ## 參考資料 * [meyr 同學](https://hackmd.io/@meyr543/B1lLmZV-5#3Exponentially-weighted-moving-average-EWMA) * [Exponential Moving Average](https://tttapa.github.io/Pages/Mathematics/Systems-and-Control-Theory/Digital-filters/Exponential%20Moving%20Average/Exponential-Moving-Average.html) * [Frequency Response of the Running Average Filter](https://ptolemy.berkeley.edu/eecs20/week12/freqResponseRA.html)