Lua 源碼分析之Table - Rehash過程

Lua的Table的內存結構主要分array part和hash part,它們倆的內存大小是動態變化的,如果空間不夠就需要分配更多的空間,如果空間利用率太少就需要縮減內存,這個過程叫做rehash。
現在來看看rehash是怎麼樣的過程。
rehash內部,主要是做了以下幾件事:
    a.計算array part的key的數量
    b.計算hash part的key的數量
    c.計算新設的key之後array part部分的數量,
    d.計算一個新的array part部分需要分配的內存大小
    e.resize。(大概過程如上,後面是每個步驟的細節,如不需要了解,可以跳到最後了。)

static void rehash (lua_State *L, Table *t, const TValue *ek) {
	  int nasize, na;
	  int nums[MAXBITS+1];  /* nums[i] = number of keys with 2^(i-1) < k <= 2^i */
	  int i;
	  int totaluse;
	  for (i=0; i<=MAXBITS; i++) nums[i] = 0;  /* reset counts */
	  nasize = numusearray(t, nums);  /* count keys in array part */
	  totaluse = nasize;  /* all those keys are integer keys */
	  totaluse += numusehash(t, nums, &nasize);  /* count keys in hash part */
	  /* count extra key */
	  nasize += countint(ek, nums);
	  totaluse++;
	  /* compute new size for array part */
	  na = computesizes(nums, &nasize);
	  /* resize the table to new computed sizes */
	  luaH_resize(L, t, nasize, totaluse - na);
}

 一、rehash過程:根據需要新設置的key,計算出array部分和hash部分:
    1.計算當前array part的數量
        (1)計算函數是numusearray(ltable.c, line:229),array part實際按2的指數增長,新增部分爲一個切片。例如1,2,4,8增長,切片分爲(1,2,3~4,5~8)四片,[2^(lg-1), 2^lg]
        (2)遍歷每一個切片,統計每個不爲nil的下標,直到統計次數大於array的長度(2的指數),同時記錄每一個切片的不爲空的數量(存爲nums, 留待後面計算使用)。
        (3)把統計結果返回,即爲array部分的數量。
        
    2.計算當前hash part的數量
        (1)計算函數爲numusehash(ltable.c, line:254),這個函數會做兩個統計:hash part的數量,以及keys爲正整數在hash part的數量。
        (2)遍歷hash部分,計算hash part的總數量
        (3)遍歷hash部分,同時計算key爲number在hash部分的數量,而且把這些數量加到array part上面去,同時所屬的切片數量nums也會加1。(注意,有一些正整數的key-value,會放在hash部分。)
        
    3.把array part和hash part的數量加起來,並且加1(加1是把新設的加進去)
    4.判斷新設的key是否屬於array part,如果是,array part的數量增加1,並且所屬性切片的數量nums也增加1。(另一層意思是:如果不是,則屬性hash part,總量已變,array part數量不變,相當於hash part總量加1)
    5.根據新設置key以後的table情況,計算array part需要分配size.
        (1)根據第1,2,4步驟,會計算到一個最新的nums數組,這個是爲最新的array part記錄
        (2)根據nums這個記錄,計算array part部分到底需要分配多少空間,計算函數是computesizes((ltable.c, line:196))。
        (3)computesizes的計算過程:
            i.根據array part的總量,遍歷每一個切片,累加遍歷到當前切片的總量。
            ii.如果累加的當前切片總量,對於當前切片假設要分配的數量,超過一半的使用率,則先記爲最優的分配size.
            舉例子來說:假設當前數組爲{1,2,3,nil, nil, nil, nil, nil, nil, nil, 10}, nums應該爲[1,1,1,0,1]
            遍歷第一個切片時,累加總量爲1,應該分配的數量爲2^0=1,使用率爲100%,當前最優的分配size爲1,在array part數量爲1;
            遍歷第二個切片時,累加總量爲1+1=2,應該分配的數量爲2^1=2,使用率爲100%,當前最優的分配size爲2,在array part數量爲2;
            遍歷第三個切片時,累加總量爲2+1=3,應該分配的數量爲2^2=4,使用率爲75%,當前最優的分配size爲4,在array part數量爲3;
            遍歷第四個切片時,累加總量爲3+0=3,應該分配的數量爲2^3=8,使用率3/8爲37.5%,最優的分配size不需要修改;
            遍歷第五個切片時,累加總量爲3+1=4,應該分配的數量爲2^4=16,使用率4/16爲25%,最優的分配size不需要修改;
            此時總量和數量總量相同,遍歷計算結束。所以最優分配size爲4, array part會3個節點被使用。

        
        6.根據計算得出的array part佔用的數量(不是分配的size)和總量相減,得出hash part的數量,用array part和hash part的總量進行resize.

 

二、最後:使用table時的建議。
        儘量可以提前分配大小,明確知道table的內容或者知道大小的,可以先預先初始化。例如:
        (1)不建議:local tb = {}; tb[1] = 1; tb[2] = 2;  tb[3] = 3; 因爲這樣會多次觸發rehash
        (2)建議:local tb = {nil, nil, nil},或者local tb = {1, 2, 3},後面再作賦值操作

 

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