ngx_hash


hash 一個常用的數據結構,一般來說 hash主要是主要關注key的散列算法和衝突處理的方法。nginx的hash對沖突處理使用的是開鏈法。並且ngx_hash是一次初始化,沒有刪除和添加方法。來看下ngx_hash的實現吧。首先還是數據結構。

typedef struct {

    ngx_str_t         key; //name

    ngx_uint_t        key_hash;//計算出來的hash

    void             *value;//保存的值

} ngx_hash_key_t;


這個數據結構很清晰,沒有什麼好特別的。

typedef struct {

    void             *value; //存儲值

    u_short           len; //長度

    u_char            name[1]; //名字 1的原因是不知道具體的長度,到時候當成指針使用。配合上面的len就可以完美的找出key

} ngx_hash_elt_t;//節點

每一個hash節點的內容。

typedef struct {

    ngx_hash_elt_t  **buckets; //保存的桶

    ngx_uint_t        size; //大小

} ngx_hash_t;

hash表的存儲內容。

typedef struct {

    ngx_hash_t       *hash; //使用哪個hash表來進行存儲。

    ngx_hash_key_pt   key; // 計算keyhash值散列算法指針


    ngx_uint_t        max_size; //最大的大小

    ngx_uint_t        bucket_size;//桶的大小


    char             *name; //名字

    ngx_pool_t       *pool;  //分配內存使用的pool

    ngx_pool_t       *temp_pool;//臨時pool

} ngx_hash_init_t;

基礎的數據結構清楚了,我們來看具體的初始化操作。

先看這個宏

#define NGX_HASH_ELT_SIZE(name)                                               \

    (sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))

計算每一個節點的大小

對應到我們的數據結構ngx_hash_elt_t 第一個sizeof(void*) 就是value的大小。 key.len +2 等於 name + len的大小 ,然後與對齊。


來看hash的初始化操作,直接從源碼來看:

ngx_int_t

ngx_hash_init(ngx_hash_init_t *hinit, ngx_hash_key_t *names, ngx_uint_t nelts)

{

    u_char          *elts;

    size_t           len;

    u_short         *test;

    ngx_uint_t       i, n, key, size, start, bucket_size;

    ngx_hash_elt_t  *elt, **buckets;


    for (n = 0; n < nelts; n++) {//循環判斷下,這個桶裝得下不

        if (hinit->bucket_size < NGX_HASH_ELT_SIZE(&names[n]) + sizeof(void *))

        {

            ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,

                          "could not build the %s, you should "

                          "increase %s_bucket_size: %i",

                          hinit->name, hinit->name, hinit->bucket_size);

            return NGX_ERROR;

        }

    }


    test = ngx_alloc(hinit->max_size * sizeof(u_short), hinit->pool->log);//分配max_size的空間

    if (test == NULL) {

        return NGX_ERROR;

    }


    bucket_size = hinit->bucket_size - sizeof(void *); //預留一個szieof(void*)大小


    start = nelts / (bucket_size / (2 * sizeof(void *)));//騷微預估下,要放多少個桶 肯定不準確卅 找個下限還是差不多的

    start = start ? start : 1;


    if (hinit->max_size > 10000 && nelts && hinit->max_size / nelts < 100) {//桶的個數特別的多,倍數沒有超過一百倍(很大的經驗成分)

        start = hinit->max_size - 1000;

    }


    for (size = start; size <= hinit->max_size; size++) { //找粗來一個差不多合適的size


        ngx_memzero(test, size * sizeof(u_short));


        for (n = 0; n < nelts; n++) {

            if (names[n].key.data == NULL) {//一般來說 感覺會蠻少的。有個key就是空,空也是值嘛

                continue;

            }


            key = names[n].key_hash % size; //散列後對應的位置咯。

            test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));//累計每個桶大小情況

            

            if (test[key] > (u_short) bucket_size) { //裝不下了

                goto next;

            }

        }


        goto found; //每一個元素都能有個桶來放了。


    next:


        continue;//這個size玩玩了不行。

    }


    size = hinit->max_size;


    ngx_log_error(NGX_LOG_WARN, hinit->pool->log, 0,

                  "could not build optimal %s, you should increase "

                  "either %s_max_size: %i or %s_bucket_size: %i; "

                  "ignoring %s_bucket_size",

                  hinit->name, hinit->name, hinit->max_size,

                  hinit->name, hinit->bucket_size, hinit->name);//很明顯是找不到合適的size來跟你玩


found:


    for (i = 0; i < size; i++) {//找到一個差不多size

        test[i] = sizeof(void *); //預留一個sizeof(void*)

    }


    for (n = 0; n < nelts; n++) {

        if (names[n].key.data == NULL) {

            continue;

        }


        key = names[n].key_hash % size; //上面一樣,散列的位置

        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); //大小加起走

    }


    len = 0;


    for (i = 0; i < size; i++) {

        if (test[i] == sizeof(void *)) {

            continue;

        }


        test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));//ngx_cacheline_size對齊

        /*

         我們知道,CACHE與內存交換的最小單位爲CACHE LINE

         假如我們的CACHE LINE64,如果我們的CACHE LINE全是對齊的,讀取一個小於等於CACHE LINE大小的cache需要一個cache

         但是如果是沒有對齊的話,一個小於等於CACHE LINE的內存放入cache的時候有可能會佔用兩個,讀的時候有可能是讀取兩個,讀取一個和兩個的區別就是類是於內存的缺頁中斷,再來一次。、

         關於對齊,到時候重新寫個文章吧

         */

        len += test[i];//需要的大小累加

    }


    if (hinit->hash == NULL) {//米有hash表給你用

        hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)

                                             + size * sizeof(ngx_hash_elt_t *));//hash的內存一起分配了

        if (hinit->hash == NULL) {

            ngx_free(test);

            return NGX_ERROR;

        }


        buckets = (ngx_hash_elt_t **)

                      ((u_char *) hinit->hash + sizeof(ngx_hash_wildcard_t));


    } else {

        buckets = ngx_pcalloc(hinit->pool, size * sizeof(ngx_hash_elt_t *));//只分配buckets

        if (buckets == NULL) {

            ngx_free(test);

            return NGX_ERROR;

        }

    }


    elts = ngx_palloc(hinit->pool, len + ngx_cacheline_size);//分配空間 但是爲啥多喃

    if (elts == NULL) {

        ngx_free(test);

        return NGX_ERROR;

    }


    elts = ngx_align_ptr(elts, ngx_cacheline_size);//這裏爲了對齊


    for (i = 0; i < size; i++) {

        if (test[i] == sizeof(void *)) {//代表木有內容

            continue;

        }


        buckets[i] = (ngx_hash_elt_t *) elts;//給他指派個空間

        elts += test[i];//移動到下一個地址


    }


    for (i = 0; i < size; i++) {//下面還要繼續用

        test[i] = 0;

    }


    for (n = 0; n < nelts; n++) {

        if (names[n].key.data == NULL) {

            continue;

        }


        key = names[n].key_hash % size;

        elt = (ngx_hash_elt_t *) ((u_char *) buckets[key] + test[key]);//找到對應的地址,後面的是地址偏移量


        elt->value = names[n].value;//保存

        elt->len = (u_short) names[n].key.len;//長度


        ngx_strlow(elt->name, names[n].key.data, names[n].key.len);//拷貝小寫過去


        test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n]));//算出下一個過來的位置

    }


    for (i = 0; i < size; i++) {//每個buckets多了sizeof(void *)

        if (buckets[i] == NULL) {

            continue;

        }


        elt = (ngx_hash_elt_t *) ((u_char *) buckets[i] + test[i]);//作用在這裏,結尾的標誌


        elt->value = NULL;

    }


    ngx_free(test); //釋放


    hinit->hash->buckets = buckets; //信息保存

    hinit->hash->size = size;



    return NGX_OK;

}

他的處理流程很簡單:

首先判斷下bucket_size是否足夠支持一個元素的存儲。

然後根據散列存儲的原則,找出一個桶個數來做了存儲

計算出每一個桶的大小。

計算出總共桶的大小。

給桶分配空間。

然後往桶裏放元素。

具體的直接參考完整的註釋。


接着 hash提供的下一個功能就是查找,咱們存進去就是爲了後面好用。


來看下查找,還是直接源碼加註釋,分析每一個步驟


void *

ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)

{

    ngx_uint_t       i;

    ngx_hash_elt_t  *elt;



    elt = hash->buckets[key % hash->size];//看下在哪個桶


    if (elt == NULL) { //這個桶沒有 代表查詢失敗

        return NULL;

    }


    while (elt->value) {//尾部 初始化的時候看過

        if (len != (size_t) elt->len) {//長度不一樣

            goto next;

        }


        for (i = 0; i < len; i++) {

            if (name[i] != elt->name[i]) {//內容不一樣

                goto next;

            }

        }


        return elt->value;//一樣的


    next:


        elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,

                                               sizeof(void *));//偏移到下一個位置

        continue;

    }


    return NULL;//沒有合適的

}


查找就特別的簡單了。



總結下:其實可以發現,預先計算出每個桶的大小,然後統一分配了內存空間,這樣可以不用使用鏈表那些來連接,節約了空間。

其實實現還是蠻簡單的,沒啥特別的地方。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章