上一篇文章提到在设置一个新的键值时,当没有可用的空间时,会出发rehash操作,而上文也提到设置键值是关乎Lua在宿主语言中运行的性能最重要的操作,而当设置键值newkey操作触发rehash时,rehash的性能又关乎到newkey的性能,具体过程参看下列详细的分析过程,其中rehash函数中的computesizes和resize函数需要重点理解。
rehash的目的
触发rehash的原因就是哈希列表没有空闲节点供新的键值设置,说明要扩展哈希列表,那么存在的问题是扩展多长的哈希列表的长度是合适的呢?这个问题就是rehash函数主要解决的问题。
rehash函数分析
1 | static void rehash (lua_State *L, Table *t, const TValue *ek) { |
countint函数分析
需要强调的是numusehash函数也是调用countint函数,以统计节点中key为数值的节点数量
1 | static int countint (const TValue *key, int *nums) { |
numusehash函数分析
1 | // totaluse是哈希列表非空节点的长度 |
computesizes函数分析
在查看这个函数的注释前,可以参考这篇文章的说明;computesizes这个函数是重新计算Table表中数组部分和哈希部分的长度,其中computesizes的参数nums数组是分片的统计数组,分片数组的统计值是分别在numusearray和countint中统计的,如下所示
分片 | -1 | 2 | 4 | 8 | 16 | 32 |
---|---|---|---|---|---|---|
数组 | nums[0] | nums[1] | nums[2] | nums[3] | nums[4] | nums[5] |
1 | static int computesizes (int nums[], int *narray) { |
resize函数分析
resize函数相对逻辑简单,只是需要注意的是重新扩容以后的赋值操作跟长度正相关。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32static void resize (lua_State *L, Table *t, int nasize, int nhsize) {
int i;
int oldasize = t->sizearray;
int oldhsize = t->lsizenode;
Node *nold = t->node; /* save old hash ... */
// 扩展数组长度
// 扩展的时候已经将原数组外的节点值设置成nil
if (nasize > oldasize) /* array part must grow? */
setarrayvector(L, t, nasize);
// 重新设置哈希列表部分
setnodevector(L, t, nhsize);
// 缩小数组长度的处理
if (nasize < oldasize) { /* array part must shrink? */
t->sizearray = nasize;
/* re-insert elements from vanishing slice */
for (i=nasize; i<oldasize; i++) {
if (!ttisnil(&t->array[i]))
setobjt2t(L, luaH_setnum(L, t, i+1), &t->array[i]);
}
/* shrink array */
luaM_reallocvector(L, t->array, oldasize, nasize, TValue);
}
// 哈希部分的节点重新插入
for (i = twoto(oldhsize) - 1; i >= 0; i--) {
Node *old = nold+i;
if (!ttisnil(gval(old)))
setobjt2t(L, luaH_set(L, t, key2tval(old)), gval(old));
}
// 释放原来的哈希数组的空间
if (nold != dummynode)
luaM_freearray(L, nold, twoto(oldhsize), Node);
}
这里要分析以下setnodevector函数的实现,这里需要说的两个重点,一个是Table表哈希部分的长度lsizenode,记录的是以2为基的次数,而实际长度是2^lsizenode,而实际lsizenode的长度也不能超过MAXBITS(26),另外Table表的lastfree是在这个函数里设置为哈希列表的最后一个节点,getfreepos函数要寻找空闲节点时是从最后一个节点向前查找。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27static void setnodevector (lua_State *L, Table *t, int size) {
int lsize;
if (size == 0) { /* no elements to hash part? */
t->node = cast(Node *, dummynode); /* use common `dummynode' */
lsize = 0;
}
else {
int i;
// 得到size的以2为底的对数
lsize = ceillog2(size);
if (lsize > MAXBITS)
luaG_runerror(L, "table overflow");
// 得到实际长度 2^lsize
size = twoto(lsize);
t->node = luaM_newvector(L, size, Node);
for (i=0; i<size; i++) {
Node *n = gnode(t, i);
gnext(n) = NULL;
setnilvalue(gkey(n));
setnilvalue(gval(n));
}
}
t->lsizenode = cast_byte(lsize);
// 为得到空闲节点设置
// 参考getfreepos函数
t->lastfree = gnode(t, size); /* all positions are free */
}