---
tags: LINUX KERNEL
---
# Linux 核心的 hash table 實作
> 本頁由 [Uduru0522](https://github.com/Uduru0522), [qwe661234](https://github.com/qwe661234), [SmallHanley](https://github.com/SmallHanley), [hankluo6](https://github.com/hankluo6), [jserv](http://wiki.csie.ncku.edu.tw/User/jserv) 貢獻
## 概況
Linux 核心如同其他複雜的資訊系統,也提供 [hash table](https://en.wikipedia.org/wiki/Hash_table) 的實作,但其原始程式碼中卻藏有間接指標 (可參見[你所不知道的 C 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list)) 的巧妙和數學奧秘。
## 間接指標的應用
Linux 核心的 hash table 實作中,用以處理 hash 數值碰撞的 `hlist_node`:
```cpp
struct hlist_node {
struct hlist_node *next, **pprev;
};
```
示意圖如下:
```graphviz
digraph G {
rankdir=LR;
node[shape=record];
map [label="hlist_head.first |<ht0> |<ht1> |<ht2> |<ht3> |<ht4> |<ht5> |<ht7> |<ht8> "];
node[shape=none]
null1 [label=NULL]
null2 [label=NULL]
subgraph cluster_1 {
style=filled;
color=lightgrey;
node [shape=record];
hn1 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 1"
}
subgraph cluster_2 {
style=filled;
color=lightgrey;
node [shape=record];
hn2 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 2"
}
subgraph cluster_3 {
style=filled;
color=lightgrey;
node [shape=record];
hn3 [label="hlist_node | {<prev>pprev | <next>next}"];
label="hash_key 3"
}
map:ht1 -> hn1
hn1:next -> hn2
hn2:next -> null1
hn2:prev:s -> hn1:next:s
map:ht5 -> hn3
hn3:next -> null2
hn1:prev:s -> map:ht1
hn3:prev:s -> map:ht5
}
```
**其中 `pprev` 為何要宣告為==指標的指標==?**
以下探討針對典型雙向鏈結串列進行節點移去時,會產生的問題。
考慮我們使用典型的 doubly-linked list:
```c
struct dll_node {
struct dll_node *next, *prev;
}
```
> `dll` 是雙向鏈結串列的簡稱
於是資料結構會展現如下圖:
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
list_head[label = "<m>list_head | <n>first"]
node_1[label = "<m>dll_node_1 | {<p>prev | <n>next}", group = list];
node_2[label = "<m>dll_node_2 | {<p>prev | <n>next}", group = list];
node_3[label = "<m>dll_node_3 | {<p>prev | <n>next}", group = list];
NULL_1, NULL_2[shape = plaintext, label = "NULL", group = list]
{rank = "min" list_head NULL_1}
list_head -> node_1:m;
node_1:p -> NULL_1
node_1:n -> node_2:m;
node_2:p -> node_1:m;
node_2:n -> node_3:m;
node_3:p -> node_2:m;
node_3:n -> NULL_2;
}
```
注意:
+ 除了末端,第一個節點的 `prev` 指標內容也是 `NULL`。
+ `prev` 及 `next` 皆指向相鄰的**節點本身**。
嘗試撰寫 `delete_node():`
```c
void dll_delete_node(struct list_head *l, struct dll_node *n)
{
struct dll_node *prev = n->prev,
*next = n->next;
if (next)
next->prev = prev;
// Delete and update where list_head points to,
// which requires the list_head to be passed in.
if (!prev) {
l->first = next;
} else {
prev->next = next;
}
}
```
可發現當我們要移除第一個節點時,必須做出額外的操作來更新 `list_head` 指向的節點,於是除了移除的目標之外,還必須傳入 `list_head`。
當我們用指標的指標改寫上述程式碼,將原本的 `struct dll_node *prev` 變更為 `struct dll_node **pprev`:
```c
struct hlist_node {
struct hlist_node *next, **pprev;
}
```
則會形成以下的結構:
```graphviz
digraph G {
rankdir = LR;
splines = false;
node[shape = "record"]
list_head[label = "<m>list_head | <n>first"]
node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list];
node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list];
node_3[label = "<m>hlist_node_3 | {<p>pprev | <n>next}", group = list];
NULL[shape = plaintext, label = "NULL", group = list]
{rank = "min" list_head}
list_head -> node_1 -> node_2 -> node_3[
weight = 10, style = invis
]
list_head:n -> node_1:m;
node_1:p -> list_head:n;
node_1:n -> node_2:m;
node_2:p -> node_1:n;
node_2:n -> node_3:m;
node_3:p -> node_2:n;
node_3:n -> NULL;
}
```
注意:
+ 僅有末端指標內容是 `NULL`。
+ `next` 指向相鄰的**節點本身**,而 `pprev` 指向**指著自身節點的指標**。
則我們便可藉由下方程式碼來撰寫 `delete_node()`:
```c
void hlist_delete_node(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
// Since there is always a pointer which points to node n,
// no special case is needed to deal with.
*pprev = next;
if (next)
next->pprev = pprev;
}
```
跟典型雙向鏈結串列的實作做比較,由於該實作的 `prev` 指標必須指向 `struct dll_node` 型別,導致第一個節點會出現指向 `NULL` 的狀況;而將其替換成指標的指標後,便可順利消除這個特例。
至此,我們理解到,`hlist` (即 hash list) 是針對雜湊表特製的鏈結串列,儘管雙向鏈結串列亦可實作雜湊表,但由於後者開頭和結尾需要各用 2 個指標,當 bucket 很大時,會增加記憶體開銷。
相較於 `list_head` 的兩個指標,`hlist_head` 僅需要保存一個指標,`hlist_node` 的 `next` 指向下個節點,但 `pprev`(指標的指標) 並不指向上個節點,而是指向上個節點的 `next` 指標,這樣設計的好處是,當要刪除的節點是首個節點,即可藉由 `*pprev = next` 直接修改指標的指向。
延伸閱讀:
* [核心基礎設施: `hlist_head`/`hlist_node` 結構解析](http://linux.laoqinren.net/kernel/hlist/)
* [hlist 資料結構圖示說明](https://zhuanlan.zhihu.com/p/82375193)
## 雜湊函數
[hash function](https://en.wikipedia.org/wiki/Hash_function) 的功能是用來將 key 值轉換成對應的 index
:::info
若將 hash function 翻譯為「哈希函數」,看似是 hash (音標:[hæʃ]) 對應到相近發音的漢語,但後者實際更接近 harsh (音標: [hɑːrʃ])的讀法,且 harsh function (在嚴苛環境得以發揮的功能) 是自動化工程的描述,與電腦科學無關。要不保留原文,要不採取意譯「雜湊」
:::
### **Perfect function**
當所有的 key 經過雜湊函數轉換後,得到 index 都不一樣時,我們稱此雜湊函數為 **perfect function**。 **Perfect function** 可以做到完美的 1-to-1,也就是一組 key 對應到一組 index。若雜湊函數的時間複雜度是 $O(1)$,這樣我們僅僅只需要時間複雜度 $O(1)$ 的時間就可以查找到 hash table 中的資料。
但實際上 **perfect function** 很難做到,我們不知道 key value 的 range 有多大,所以不知道 bucket 的數量要多少才能滿足 1-to-1,若 hash table size 很大,但其中有很多空間都沒有使用到也會造成不必要的浪費。由於多個 key value 可能對應到同一個 index,所以一個 index 會對應到一個可以存數筆 data 的 bucket。
綜合以上,我們只能透過設計雜湊函數,使其盡可能接近 **perfect function**。
### 雜湊函數考慮因素
1. 計算所需的時間複雜度要低
希望雜湊運算的時間複雜度為 $O(1)$。
2. 減少碰撞
碰撞 (collision) 是指不同的 key 對應到相同的 index,碰撞越少,則越接近 **perfect function**。
3. 避免 Clustering 現象
Clustering 是指某些 bucket 存放很多筆資料,而某些 bucket 存放的很少,應該盡量讓 bucket 存放的資料筆數平衡,否則容易造成 `Overflow`,這樣會造成存取效率變差。
`Overflow` 是指 bucket 的記憶體空間滿了,需要刪除一筆資料才能存入新的資料,`Overflow` 發生時,決定哪一筆資料要被刪除也是重要的議題。
### 常見雜湊函數作法
- [ ] Division method
h(k) = $k \% N$
以 mod 運算後的餘數作為 index,由於 key 值得範圍無法得知,所以如何挑選適合的 $N$ 很關鍵。
- [ ] Mid-square
h(k) = $bits_{i,i + r - 1}(k^2)$
將 k 值 (即 `key`) 平方後,取其第 $i$ ~ $i + r - 1$ 位元,得到的數字範圍會是 0 ~ $2^r - 1$,所以 bucket 數量為 $2^r$ 即可。
例如:
key = 6,$6^2 = 36$,假設 i = 2, r = 3,所以取其第 2 ~ 4 位元,又 $36_{10}$ = $100100_2$,取第 2 ~ 4 位元得 $010_2$,所以 index = $2_{10}$。
- [ ] Folding addition
先將 key 切割成片段後相加,亦可以對相加後的結果做其他運算。
例如:
key = 3823749374,將此值每三個數字分成一段
```
382 | 374 | 937 | 4
```
再將這四個數字相加
index = 382 + 374 + 937 + 4 = 1697
可以進一步對 1697 進行其他運算例如: mod, 反轉...
- [ ] Multiplication Method
由於大部分的情況下,我們不知道 key 的範圍以及分佈情形,這樣的情形適用 `Multiplication Method`,步驟如下:
![](https://hackmd.io/_uploads/SJg4Pal0o.png)
key 乘上 constant A $\to$ 取小數點部分 $\to$ 再乘上 m $\to$ 再取整數部分的一系列步驟讓雜湊函數得以儘量把更多的 key 中的 bit 納入考慮而增加隨機性。 (原因可見 [Hash Table:Intro(簡介)](http://alrightchiu.github.io/SecondRound/hash-tableintrojian-jie.html#dm))
* K: key value
* A: a constant, 且 0 < A < 1
* m: bucket 數量, 且 m = $2^p$
## Linux 核心的雜湊函數
Linux 核心的 [hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 中,雜湊函數的設計是使用上面所提到的 Multiplication Method,但上面提到的雜湊函數本是形如 $h(K) = \lfloor m \times (KA - \lfloor KA \rfloor) \rfloor$,但在 [hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 中卻用到 bit-shifting 的方式,這是由於上述的 funciton 與 $h(K) = K \times 2^w \times A >> (w - p)$ 是等價的, 而且 bit-shifting 的效率較好, 所以以這種方式來實作。
* K: key value
* A: a constant, 且 0 < A < 1
* m: bucket 數量, 且 m = $2^p$
* $w$: 一個 word 有幾個 bit
### 為何這二個函數等價?
假設一個 word 是 8 個 bit,將 KA 的結果以 8 bit 整數配上 8 bit 浮點數表示
```graphviz
graph structs {
node[shape=record];
struct1 [label="0|0|0|0|0|1|0|1"]
node[shape=point]
dot
node[shape=record];
struct2 [label="1|1|1|0|1|0|0|0"]
}
```
根據 $h(K) = \lfloor m * (KA - \lfloor KA \rfloor) \rfloor$,我們只需要 KA 的浮點數部分,也就是後面的 8 個位元,再假設 m 是 8 = $2^3$,再乘上 $m$,於是以上步驟等同於清除前 8 個位元再左移 3 個位元
```graphviz
graph structs {
node[shape=record];
struct1 [label="0|0|0|0|0|1|1|1"]
node[shape=point]
dot
node[shape=record];
struct2 [label="0|1|0|0|0|0|0|0"]
}
```
最後做下高斯只留下整數部分
```graphviz
graph structs {
node[shape=record];
struct1 [label="0|0|0|0|0|1|1|1"]
node[shape=point]
}
```
而這樣的操作等同於 KA 的結果先左移 8 個位元,這樣前面 8 個位元即為 KA 結果的浮點數部分,依照前面的操作,我們可知浮點數部分其實只要留下前 p 個位元,所以要右移 $(w - p)$ 個位元。
KA
```graphviz
graph structs {
node[shape=record];
struct1 [label="0|0|0|0|0|1|0|1"]
node[shape=point]
dot
node[shape=record];
struct2 [label="1|1|1|0|1|0|0|0"]
}
```
$K \times 2^w \times A$,乘上 $2^w$ 等同於左移 8 個位元
```graphviz
graph structs {
node[shape=record];
struct1 [label="1|1|1|0|1|0|0|0"]
node[shape=point]
dot
node[shape=record];
struct2 [label="0|0|0|0|0|0|0|0"]
}
```
接著右移 $(w - p)$,也就是 $8 - 3 = 5$ 個位元,因為在 Linux 實際用 `uint` 型態,因此只會留下整數部分。
```graphviz
graph structs {
node[shape=record];
struct1 [label="0|0|0|0|0|1|1|1"]
node[shape=point]
}
```
### Linux 原始程式碼
以下針對 32 位元的雜湊函數來說明。
> 64 位元的雜湊函數邏輯相同,只是 word size 為 64
> [include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h)
```c
#define GOLDEN_RATIO_32 0x61C88647
#ifndef HAVE_ARCH__HASH_32
#define __hash_32 __hash_32_generic
#endif
static inline u32 __hash_32_generic(u32 val)
{
return val * GOLDEN_RATIO_32;
}
static inline u32 hash_32(u32 val, unsigned int bits)
{
/* High bits are more random, so use them. */
return __hash_32(val) >> (32 - bits);
}
```
* `GOLDEN_RATIO_32`: $A \times 2^{32}$ 後的結果
* `__hash_32_generic` (u32 型態)
* val: key $K$
* 該函式計算 $K \times 2^w \times A$ 的結果
* `hash_32` (u32 型態)
* val: key $K$
* bits: 其實就是 p, $2^{bits}$ = bucket 數量
* 這個函式將 `__hash_32_generic(u32 val)` 算出的結果 $K \times 2^w \times A$ 右移 `(32 - p)` 個位元
[golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) 在數學上為 $\varphi=(\sqrt{5}+1)/2\approx 1.618033989$,我們可計算出其倒數 $\varphi^{-1}= (\sqrt{5}-1)/2\approx 0.618033989$ 恰好是原本的小數部份。〈[Linux Kernel Hash Table Behavior: Analysis and Improvements](http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf)〉的第 5.1 節給出
> $(\sqrt{5} - 1 ) / 2 = 0.618033989$
$2654435761 / 4294967296 = 0.618033987$
這個 $4294967296$ 顯然是 32 位元無號數的最大值加 1 ,亦即 $2^{32}$ 。
這代表我們將輸入的 `val` 乘上 $\varphi^{-1}$ 幾乎等同於乘上 2654435761 並右移 32 ,然後我們需要左移 `bits` 個位元以充分利用 hash table 的空間。
因此, `val * GOLDEN_RATIO_32 >> (32 - bits)` $\equiv$ $K \times A \times 2^w >> (w - p)$
### GOLDEN_RATIO
![](https://hackmd.io/_uploads/HJqiOClCi.png)
文藝復興時期,黃金分割 ([golden ratio](https://en.wikipedia.org/wiki/Golden_ratio)) 被視為最神聖的比例。達文奇在 《[繪畫論](https://en.wikipedia.org/wiki/A_Treatise_on_Painting)》一書指出:
> 「美感完全建立在各部分之間神聖比例的關係上,各特徵必須同時作用,才能產生使觀眾往往如醉如痴的和諧比例」
黃金分割在數學、美學、人體、藝術、自然等領域,啟發人們去認識世界和改造世界,因此 16 世紀威尼斯數學家 Luca Pacioli (有時也寫為 Paccioli 或 Paciolo) 將黃金分割稱為「神賜的比例」。
至於 A 為什麼使用 [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio),也就是 A = $\dfrac{\sqrt{5} - 1}{2}$ = 0.6180339887...呢?該數值是由 [Donald Knuth](https://en.wikipedia.org/wiki/Donald_Knuth) 建議,[Hashing and Hash Tables 課程講義](http://www.compsci.hunter.cuny.edu/~sweiss/course_materials/csci335/lecture_notes/chapter05.pdf) 提出實驗結果如下圖,其中當 A = [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio) 時, 此雜湊函數稱為 Fibonacci hashing,且 key 經此函數轉換得到的 index 相當分散, 意即碰撞很少。
![](https://hackmd.io/_uploads/H1f3Yax0i.png)
### 比較 golden ratio 與非 golden ratio
以下挑選 4 個不同的數字作為 $A \times 2^{32}$,並紀錄對 key: 0 ~ 1500 做 `hash_32(key value, 10)` 的結果
1. `0x61C88647`
2. `0x80000000`
3. `0x12345678`
4. `0x54061094`
圖的 x 軸是 key,y 軸是 `hash_32(key value, 10)` 計算出的值
![](https://hackmd.io/_uploads/H15TYpxRi.png =50%x)![](https://hackmd.io/_uploads/ByS15aeRj.png =50%x)
![](https://hackmd.io/_uploads/H1bZqpgAs.png =50%x)![](https://hackmd.io/_uploads/S1-M96xAj.png =50%x)
由實驗個果圖可發現, golden ratio 作為 A 結果最均勻分散,另外結果也說明 A 值的選擇對於碰撞有很大的影響, 例如 $A \times 2^{32}$ = 0x80000000 時,0 ~ 1500 只有 2 種可能,不僅每兩個數值就發生一次碰撞,由於資料只會存放於兩個 bucket,因此 Cluster 程度相當嚴重。
在 [tools/include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h) 可發現一個巨集 `GOLDEN_RATIO_PRIME`,
```c
/*
* Knuth recommends primes in approximately golden ratio to the maximum
* integer representable by a machine word for multiplicative hashing.
* Chuck Lever verified the effectiveness of this technique:
* http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
*
* These primes are chosen to be bit-sparse, that is operations on
* them can use shifts and additions instead of multiplications for
* machines where multiplications are slow.
*/
#if BITS_PER_LONG == 32
/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
#define GOLDEN_RATIO_PRIME 0x9e370001UL
#elif BITS_PER_LONG == 64
/* 2^63 + 2^61 - 2^57 + 2^54 - 2^51 - 2^18 + 1 */
#define GOLDEN_RATIO_PRIME 0x9e37fffffffc0001UL
#else
#error Define GOLDEN_RATIO_PRIME for your wordsize.
#endif
```
在 [commit 689de1d](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/include/linux/hash.h?id=689de1d6ca95b3b5bd8ee446863bf81a4883ea25) 後改成以下:
```c
/*
* This hash multiplies the input by a large odd number and takes the
* high bits. Since multiplication propagates changes to the most
* significant end only, it is essential that the high bits of the
* product be used for the hash value.
*
* Chuck Lever verified the effectiveness of this technique:
* http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
*
* Although a random odd number will do, it turns out that the golden
* ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice
* properties. (See Knuth vol 3, section 6.4, exercise 9.)
*
* These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2,
* which is very slightly easier to multiply by and makes no
* difference to the hash distribution.
*/
#define GOLDEN_RATIO_32 0x61C88647
#define GOLDEN_RATIO_64 0x61C8864680B583EBull
```
:::info
註解中的 `phi = (sqrt(5)-1)/2` 指的是 $\phi^{-1}$,為 golden ratio 的倒數。
:::
* $\text{0x61C88647}=\text{1640531527}\approx2^{32}\times\phi^{-2}=2^{32}\times\frac{3-\sqrt{5}}{2}$
* $\text{0x61C8864680B583EB}=\text{7046029254386353131}\approx2^{64}\times\phi^{-2}=2^{64}\times\frac{3-\sqrt{5}}{2}$
任意數乘上這兩個數再取其高位元,可呈現好的數值分布。在連續的數值中,利用這種 Fibonacci Hashing 方法可將 hash 後對應的值分散到其他 hash value 的值之間 (將最大區間以 golden ratio 分割的點)。
![](https://hackmd.io/_uploads/SkU0fmW0o.png)
而現實生活上的 keys 值分布常常會有某種數值分布,類似 $\{K, K+d, K+2d, ..., K+td\}$,例如字串集合 $\{"PART1", "PART2", "PART3"\}$,這種情況利用此種 hash function 的行為就如同計算 $h(0), h(1), h(2)$,便能將 key 值映射到沒有使用過的 value,減少碰撞的機率。
假設 $\theta$ 為一無理數,要將 $\{\theta, 2\theta, 3\theta, ..., n\theta\}$ 的小數部分放到 $[0, 1]$ 之間,則會發現 $(n+1)\theta$ 放置的位置會在由前面的點分割成的線段中最長的線段,而當 $\theta$ 過大或過小時 (接近 0 或 1),產生的分布區間便會由小而大,並不是均勻分布。
其中討論到有關 `GOLDEN_RATIO_PRIME` 的實作考量,出自於《[The Art of Computer Programming](https://www-cs-faculty.stanford.edu/~knuth/taocp.html)》(Knuth vol 3, section 6.4, exercise 9) 這本書,證明能產生較好的分布的數值為 $\phi^{-1}$ 及 $\phi^{-2}$以下取自這本書的片段。
**Theorem S.** Let $\theta$ be any irrational number. When the points {$\theta$}, {$2\theta$}, ..., {$n\theta$} are placed in the line segment [0 ... 1], then $n + 1$ line segments formed have at most three different lengths. Moreover, the next point {$(n + 1)\theta$} will fall in one of the largest existing segments.
(用 Three-gap theorem 推導。)
:::info
{$n \theta$} 代表 $n \theta$ 的小數部分。
:::
之所以要取小數部分,原理是該分割問題可轉化為 [Three-gap theorem](https://en.wikipedia.org/wiki/Three-gap_theorem)。line segment [0 … 1] 對應到 Three-gap theorem 中周長為 1 的圓,$n \theta$ 的整數部分相當於繞了圓多少圈 (即在 line segment 循環多少次),而小數部分相當於新的點坐落於圓上的哪一個位置 (即 line segment 中新分割點的位置)。
有關 **Theorem S** 的證明在 exercise 8
![](https://hackmd.io/_uploads/r1SZjagCi.png)
![](https://hackmd.io/_uploads/HJ-Gsag0i.png)
實際的雜湊函數 (以 32 位元為例) 定義於 [tools/include/linux/hash.h](https://github.com/torvalds/linux/blob/master/tools/include/linux/hash.h#L57-L69):
```c
#ifndef HAVE_ARCH__HASH_32
#define __hash_32 __hash_32_generic
#endif
static inline u32 __hash_32_generic(u32 val)
{
return val * GOLDEN_RATIO_32;
}
static inline u32 hash_32(u32 val, unsigned int bits)
{
/* High bits are more random, so use them. */
return __hash_32(val) >> (32 - bits);
}
```
上述程式碼以 32 位元無號整數來表達介於 0 到 1 之間的浮點數,其中 `__hash_32_generic` 將欲進行 hash 的數值 `val` 乘上 `GOLDEN_RATIO_32` ,而 `hash_32` 則取最高的 `bits` 個位元。對應上述 Fibonacci Hashing,`val` 對應到 $n$,`GOLDEN_RATIO_32` 為 $\phi^{-1}$ 以 32 位元無號整數表示,兩者相乘,因為會產生溢位 (一次溢位相當於 Three-gap theorem 中繞了圓一圈),相乘的結果為 {$n \phi^{-1}$} 以 32 位元無號整數表示,取其高位元,則相當於取小數前幾位數。
對比前後版本的 `GOLDEN_RATIO_PRIME` 實作,新的實作可讓雜湊函數結果的分佈更為均勻。以下分別測試 0 到 1000、10000 映射到 hash table 不同 locations 的次數,hash table 的大小為 1024 (10 bits):
![](https://hackmd.io/_uploads/rJW4iaeRs.png)
![](https://hackmd.io/_uploads/B1WSs6gRs.png)
### Reference:
* [Hashing 基礎介紹](https://meteorv.dev/Data-Structure/hashing/)
* [Hash Table 簡介](http://alrightchiu.github.io/SecondRound/hash-tableintrojian-jie.html)
* [Hash Functions](https://www.cs.hmc.edu/~geoff/classes/hmc.cs070.200101/homework10/hashfuncs.html)
* [Data Structures: Hashing II](http://www.cs.nthu.edu.tw/~wkhon/ds/ds12/lecture/lecture18.pdf)
* [Hashing and Hash Tables](http://www.compsci.hunter.cuny.edu/~sweiss/course_materials/csci335/lecture_notes/chapter05.pdf)