contributed by < JimmyLiu0530
>
進階電腦系統理論與實作
bfloat16 浮點數格式由 Google 公司發展,最初用於該公司第三代 Tensor 處理單元 (Cloud TPU)。bfloat16 的主要想法是提供 16 位元浮點數格式,其動態範圍與標準 IEEE 754 的 FP32 (Single-precision floating-point format) 相同,但精度較低,相當於指數區和 FP32 保持相同的 8 位元,並將 FP32 的 fraction 區域縮減到 7 位元。
BFloat16 的範例
下列是個轉換的程式:
對應的測試程式:
參考執行結果:
程式上半段主要處理 0
、 infinite
及 NaN
。BFloat16 的這三種特別值的編碼與 float32 差不多,只差在 significand 的位數而已,如下圖所示:
val | BFloat16 |
---|---|
+0 | 0 00000000 0000000 |
-0 | 1 00000000 0000000 |
+inf | 0 11111111 0000000 |
-inf | 1 11111111 0000000 |
+NaN | 0 11111111 非0 |
-NaN | 1 11111111 非0 |
程式碼下半段則是處理進位,這裡是採進位到最靠近的數 (Round to nearest)。那要如何操作呢? 我們先觀察下面的幾個例子:
舉 1.200000
為例,16進位為 0x3f99999a,因為此值超過 0x3f998000 (即 0x3f990000 與 0x3f9a0000 的中點),因此將來會進位成 0x3F9AXXXX。
舉 3.460000
為例,16進位為 0x405d70a4,因為此值沒超過 0x405d8000 (即 0x405d0000 與 0x405e0000 的中點),因此將來不會進位,仍是 0x405dXXXX。
由上面的例子可以看出,是否會進位到下一個數其實是由第 15 個位元決定的,因為若第 15 個位元為 1,則代表此值一定在中點上或超過中點,因此需要進位到下一個數值。若第 15 個位元為 0,則代表不超過中點,因此不需要進位。綜合上面兩種可能,我們只需要對第 15 位元 +1
,即可實現進位或不進位。
那要如何針對原本的 float32 的第 15 位元 +1
? (假設浮點數為 )
首先,取得該值的 exp,對應到程式碼 line 15,因此 BB1 = 0xff800000
。此時,r
會等於 。
接著再執行 r/= 256;
,r
會變成 。
之後 line 17: = = ,而 就會針對第 15 個位元+1。
最後因為 BFloat16 只使用 16 位元,BB2 = 0xffff0000
,只留下最左邊 16 個位元。
Note:pencil:
可以將
替換成
會得到相同的結果。
考慮以下 ring buffer 的實作:
其測試程式如下:
ring buffer 也就是我們熟知的 circular queue,如下圖所示:
這種資料結構有兩個索引 start
及 end
分別紀錄第一個及最後插入元素的索引。以下是將 start
改指向下一個元素的操作:
,因此 RB1 = 1
。這邊因為是 ring buffer 的結構,所以如果原本 start
已經等於 (BUF)->size
,那麼下一個元素就會回到 index = 0。
同理,end
的下一個元素也會是:
,因此 RB2 = 1
。
此外,ring buffer 採用 FIFO
,所以在 ringbuf_write
中分成兩個動作:
end
目前所指的格子end
移往下一格,並檢查移動完的 end
是否跟 start
指到同一格。若是,則將來寫入時會覆蓋掉此格,因此 start
需移往下一格。同樣地,在 ringbuf_read
中也是分成兩個動作,只是變成:
ELEMENT
start
移往下一格最後,為何在 macro 中會使用到 do{...} while(0)
,這看似多餘的寫法背後到底隱含甚麼意義?
先考慮下面的例子:
當此程式 compile 時,macro 會被展開成下面的樣子:
那如果換成這樣:
雖然為了解決這個問題,避免了 compile error,但不符合一般的寫法,也不易讀。
最好的方法就是:
展開之後會變成
就不會再跑出 compile error了。
考慮到以下靜態初始化的 singly-linked list 實作:
其執行結果為:
透過觀察執行結果的第一行 9547
,我們可以知道這個鏈結串列會是:
再根據 line 4 及 39,就可以得到 A = 7
, B = 4
, C = 5
, D = 9
。
其原理就是利用 (參考 C 語言程式設計技巧)
意同於
C99 [6.5.2.5] Compound literals
- The type name shall specify an object type or an array of unknown size, but not a variable length array type
- A postfix expression that consists of a parenthesized type name followed by a braceenclosed list of initializers is a compound literal. It provides an unnamed object whose value is given by the initializer list.
- If the type name specifies an array of unknown size, the size is determined by the initializer list as specified in 6.7.8, and the type of the compound literal is that of the completed array type. Otherwise (when the type name specifies an object type), the type of the compound literal is that specified by the type name. In either case, the result is an lvalue.
接著,當呼叫 sort
後會有兩個指標 current
及 next
分別指向目前的節點及下一個節點,再呼叫 sorted_insert
將已經排序好的鏈結串列 sorted
與 current
進行排序。結束後, current
往下一個節點移動,重複以上步驟直到 current
指向 NULL。
在 sorted_insert
中,如果傳入的 sorted
串列為空,或是傳入的 node->val
比目前以排序好的串列首的 val
還小,那麼直接 BB1 = node->next = *head;
,BB2 = *head = node;
。
傳入的 sorted
串列為空:
或傳入的 node->val
比目前以排序好的串列首的 val
還小 (2 < 5):
執行後會變成:
或
否則,node
要跟 sorted
內的節點一個一個比,直到遇到一個節點其 val
比 node->val
大的,並差在這個節點的前面。
- Fundamental Sorting Algorithms 提到 tiled merge sort 此種 cache-aware 演算法的考量點,對於排序大量資料而言,quick sort 和 merge sort 勝過 insertion sort ( ,對於小量資料則取決於執行環境。因此在 merge sort 將問題切成 L1 cache size 後,即不再往下遞迴,改用 insertion sort 進行排序。
- 交叉閱讀共筆 eecheng87/quiz3
給定一個整數序列,其中會有一個數值重複,請找出。
已知條件:
考慮以下程式碼是可能的解法:
根據鴿籠原理,長度 n
,數值範圍:[1, n-1]
的陣列至少會有兩元素重複,因此接下來我們要找出重複的數值是多少。
此方法就是 一個位元一個位元去比較所有 nums[]
以及 [0,..,n-1]
1 的個數。
如果在某位元 k, nums[]
比 [0,1,...,n-1]
擁有更多的 1,那麼就代表重複的數值的第 k 位元必為 1,所以 CCC = c2 > c1
。如此重複地檢查每個位元,即可求出重複值。
至於需要檢查多少個位元,在 line 4-5 因為陣列長度為 numsSize
,所以所有數值皆小於 numsSize
。而 numsSize
需要 位元來表示其數值,所以只需檢查這 個位元即可。
執行效能:
此方法的時間複雜度: , 為陣列大小。
程式說明:
由於陣列中的數值被限制在 [1, n-1] 之間,因此這些數值又可以視為陣列的索引。當我們從 index = 0 開始走訪此陣列,過一陣子會發現我們無法離開這個陣列,這是因為陣列中有重複數值的緣故 (重複值代表他們指向同一個 index),才會形成一個 circle
,而 entry of circle 就會是重複值。因此,整個問題轉化成找此 circle 的 entry point。
Entry of circle (Floyd's Tortoise and Hare algorithm)
簡單來說,一開始都從起始點 (list start) 開始走,烏龜一次走一步,兔子一次走兩步,經過一段時間會在 meeting point 相遇 (對應到程式 line 3-7)。相遇後,將兔子的速度降至與烏龜相同,並且將兔子移至起始點後,再讓他們繼續走 (對應到程式 line 9-13)。最後他們第一次相遇的地方就會是 entry of circle。
詳細的說明這邊有。