本文讲述: nginx 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;
/* nginx hash结构大致是这样:
hash结构中有N个桶, 每个桶存放N个元素(即<k,v>),在内存中,
用一个指针数组记录N个桶的地址,每个桶又是一个 ngx_hash_elt_t 数组
指针数组 和 ngx_hash_elt_t 数组 在一个连续的内存中.
优点: 使用数组提高寻址速度
缺点: hash表初始化后,只能查询,不能修改.
当然hash结构本来就是数组形式,但对于冲突的元素大多是用链表形式存放,再挂载到hash数组上.
nginx的hash结构过程:
首先 每个桶的空间大小固定 通过 ngx_hash_init_t.bucket_size 指定;
然后 根据元素的个数和桶的固定大小计算出需要多少个桶.
然后 计算哪些元素存放到哪个桶中,方法就是 (元素的hash值 % 桶的个数)
这时 需要多少个桶,这些桶需要多少内存空间,每个桶存放多少元素,需要多少内存空间就知道,
申请所有桶的内存空间,即为 ngx_hash_init_t.hash.buckets 指针数组.
申请每个桶存放元素的存储空间 = 该桶元素占用的内存空间 + void指针
为了提高查询效率,申请一个连续内存空间存放 所有桶的元素.
然后把这片连续的内存空间映射到 ngx_hash_init_t.hash.buckets 指针数组.
然后为每个桶的元素赋值.
最后将每个桶的"结束元素"置为NULL
void指针的用途: 为桶的结束标记, 在 ngx_hash_find() 遍历桶是判断 etl->value 是否为NULL时用到.
你可能有以为 "结束元素"只有一个void*指针的空间转换成 ngx_hash_elt_t 后会不会越界操作?
答案是不会的, void*指针的空间转换成 ngx_hash_elt_t 后只操作 ngx_hash_elt_t.value ,
而 ngx_hash_elt_t.value 刚好只占 void指针空间大小.
指定桶的大小的好处: 保证每个桶存放元素的个数不超过一定值,目的是为了提高查询效率.
*/
/* 每个桶至少能存放一个元素 + 一个void指针,
指针的目的是为了
*/
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);
if (test == NULL) {
return NGX_ERROR;
}
bucket_size = hinit->bucket_size - sizeof(void *);
/* 计算需要桶数目的下界
每个元素最少需要 NGX_HASH_ELT_SIZE(&name[n]) > (2*sizeof(void*)) 的空间
因此 bucket_size 大小的桶最多能容下 bucket_size/(2*sizeof(void*)) 个元素
因此 nelts 个元素就最少需要start个桶。
*/
start = nelts / (bucket_size / (2 * sizeof(void *)));
start = start ? start : 1;
if (hinit->max_size > 10000 && hinit->max_size / nelts < 99) {
start = hinit->max_size - 1000;
}
/* 从最小桶数目开始试,计算容下 nelts 个元素需要多少个桶 */
for (size = start; size < hinit->max_size; size++) {
ngx_memzero(test, size * sizeof(u_short));
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]));
#if 0
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %ui %ui \"%V\"",
size, key, test[key], &names[n].key);
#endif
if (test[key] > (u_short) bucket_size) { /* 说明 size 个桶,容不下 nelts 个元素 */
goto next;
}
}
goto found;
next:
continue;
}
ngx_log_error(NGX_LOG_EMERG, hinit->pool->log, 0,
"could not build the %s, you should increase "
"either %s_max_size: %i or %s_bucket_size: %i",
hinit->name, hinit->name, hinit->max_size,
hinit->name, hinit->bucket_size);
ngx_free(test);
return NGX_ERROR;
found:
/* 执行到这里就得到了 容下 nelts 个元素需要 size 个桶 */
for (i = 0; i < size; i++) {
test[i] = sizeof(void *); /* 初始化每个桶的大小 */
}
for (n = 0; n < nelts; n++) {
if (names[n].key.data == NULL) {
continue;
}
key = names[n].key_hash % size; /* 计算第N个元素放置到哪个桶中 */
test[key] = (u_short) (test[key] + NGX_HASH_ELT_SIZE(&names[n])); /* 这个桶的空间扩大到 容下第 n 个元素 */
}
len = 0;
/* 将每个桶的实际大小对应到 cacheline 并计算数所用桶的总大小 */
for (i = 0; i < size; i++) {
if (test[i] == sizeof(void *)) { /* 说明这个桶中没有插入元素 */
continue;
}
test[i] = (u_short) (ngx_align(test[i], ngx_cacheline_size));
len += test[i];
}
/* 如果hash为NULL, 就分配 ngx_hash_wildcard_t 结构,
照 ngx_hash_init_t 的结构来说 应该分配 ngx_hash_t.
这里分配 ngx_hash_wildcard_t 不知道有啥额外的用途.
*/
if (hinit->hash == NULL) {
hinit->hash = ngx_pcalloc(hinit->pool, sizeof(ngx_hash_wildcard_t)
+ size * sizeof(ngx_hash_elt_t *));
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 *));
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;
}
/* 将 names 中的元素 赋值给 对应桶中的 ngx_hash_elt_t 数组中的元素,
names的元素在 ngx_hash_elt_t 数组的位置是无序的.
*/
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]));
}
/* 设置每个桶的结束元素为NULL */
for (i = 0; i < size; i++) {
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;
#if 0
for (i = 0; i < size; i++) {
ngx_str_t val;
ngx_uint_t key;
elt = buckets[i];
if (elt == NULL) {
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: NULL", i);
continue;
}
while (elt->value) {
val.len = elt->len;
val.data = &elt->name[0];
key = hinit->key(val.data, val.len);
ngx_log_error(NGX_LOG_ALERT, hinit->pool->log, 0,
"%ui: %p \"%V\" %ui", i, elt, &val, key);
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
}
}
#endif
return NGX_OK;
}