快速略读了一下源码,记了一些东西。
先看看mapping
mapping其实就是C++中的multimap,但是支持更多。
array values(mapping)。这个方法可以返回所有mapping中的value,那么values()究竟作了什么呢?源码中是这样定义的:
1 PMOD_EXPORT struct array *mapping_values(struct mapping *m) 2 { 3 INT32 e; 4 struct keypair *k; 5 struct array *a; 6 struct svalue *s; 7 8 #ifdef PIKE_DEBUG 9 if(m->data->refs <=0) 10 Pike_fatal("Zero refs in mapping->data "); 11 #endif 12 13 check_mapping_for_destruct(m); 14 15 a=allocate_array(m->data->size); 16 s=ITEM(a); 17 18 /* no locking required */ 19 NEW_MAPPING_LOOP(m->data) assign_svalue(s++, & k->val); 20 21 a->type_field = m->data->val_types; 22 23 #ifdef PIKE_DEBUG 24 if(d_flag > 1) check_mapping_type_fields(m); 25 #endif 26 27 return a; 28 }
可以看到,在第15行直接申请了足够存储所有value的内存大小,第19行是一个宏,其循环了所有的可能的value,然后assign_svalue就会将每个k->value给拷贝到返回值array中,并且在27行返回a。
mapping也可以将字符串作为key的,但是它是如何依靠一个ke=string来迅速地找到对应的value的呢?其实mapping使用的siphash(维基百科, 原著论文)来哈希字符串的,也就是先将字符串哈希成一个值,再跟其他的key的哈希值来匹配,这样就容易找到了。如果有兴趣可以看pike中的实现:
1 static size_t low_hashmem_siphash24( const void *s, size_t len, size_t nbytes, size_t key ) 2 { 3 const unsigned char * in = (const unsigned char*)s; 4 unsigned long long inlen = MINIMUM(len, nbytes); 5 6 /* "some pseudo randomly generated bytes" 伪随机产生的字节 */ 7 unsigned INT64 v0 = 0x736f6d6570736575ULL; 8 unsigned INT64 v1 = 0x646f72616e646f6dULL; 9 unsigned INT64 v2 = 0x6c7967656e657261ULL; 10 unsigned INT64 v3 = 0x7465646279746573ULL; 11 unsigned INT64 b; 12 unsigned INT64 k0 = (unsigned INT64)key; 13 unsigned INT64 k1 = (unsigned INT64)key; 14 unsigned INT64 m; 15 const unsigned char *end = in + inlen - ( inlen % sizeof( unsigned INT64 ) ); 16 const int left = inlen & 7; 17 b = ( ( unsigned INT64 )inlen ) << 56; 18 v3 ^= k1; 19 v2 ^= k0; 20 v1 ^= k1; 21 v0 ^= k0; 22 23 for ( ; in != end; in += 8 ) 24 { 25 m = U8TO64_LE( in ); 26 v3 ^= m; 27 SIPROUND; 28 SIPROUND; 29 v0 ^= m; 30 } 31 32 switch( left ) 33 { 34 case 7: b |= ( ( unsigned INT64 )in[ 6] ) << 48; 35 36 case 6: b |= ( ( unsigned INT64 )in[ 5] ) << 40; 37 38 case 5: b |= ( ( unsigned INT64 )in[ 4] ) << 32; 39 40 case 4: b |= ( ( unsigned INT64 )in[ 3] ) << 24; 41 42 case 3: b |= ( ( unsigned INT64 )in[ 2] ) << 16; 43 44 case 2: b |= ( ( unsigned INT64 )in[ 1] ) << 8; 45 46 case 1: b |= ( ( unsigned INT64 )in[ 0] ); break; 47 48 case 0: break; 49 } 50 51 v3 ^= b; 52 SIPROUND; 53 SIPROUND; 54 v0 ^= b; 55 v2 ^= 0xff; 56 SIPROUND; 57 SIPROUND; 58 SIPROUND; 59 SIPROUND; 60 b = v0 ^ v1 ^ v2 ^ v3; 61 return (size_t)b; 62 }
将字符串哈希成一个值后,pike是以一个表来管理字符串的(不知是否为所有),定义在stralloc.c中的原型如下:
static struct pike_string **base_table=0;
shared string table。当字符串被哈希成一个值后,就在这个table中寻找(这是简化了的说法,其实在找之前还与htable_mask做了位与)。但是哈希值是不能保证唯一的,所以需要一个二级指针,每个桶中存放着所有的哈希值相同的字符串呢。那如果重叠率太高了呢?pike还定义了一个全局变量:
static unsigned int need_new_hashkey_depth=0;
这个变量在每次找不到的时候就可以会有变化,这跟“字符串哈希”所取的前缀长短有关(如果每次都将整个字符串用于哈希的话,也太长了吧?其实不知道是否是整个字符串哈希),可能是会根据这个变量值作哈希的调整。函数原型如下:
static struct pike_string *internal_findstring(const char *s, ptrdiff_t len, enum size_shift size_shift, size_t hval) /* hval是用siphash计算出来的结果 */
每对key-value的信息是以一个struct keypair来记录的,其中仅记录了一些信息,以及一个指针(应该是指向真正数据的),初始mapping时需要申请一些内存来存放keypair。
#define MD_KEYPAIRS(MD, HSIZE) ( (struct keypair *) DO_ALIGN( PTR_TO_INT(MD) + OFFSETOF(mapping_data, hash) + HSIZE * sizeof(struct keypair *), ALIGNOF(struct keypair)) )
DO_ALIGN的定义:
#define DO_ALIGN(X,Y) (((size_t)(X)+((Y)-1)) & ~((Y)-1))
ALIGNIOF是内置函数,用于计算对齐内存所占的大小。
mapping中提用了几个操作,很是方便,比如 &与操作,|或操作,xor异或操作等等。
先拿xor开刀,就是将两个mapping中的具有相同key的key-value都给删除掉,留下唯一的key的那种。实现起来也没有多大的优化技巧,就是先将小的mapping先拷贝出来,然后再将大的mapping中的key-value逐个往里面插,如果key已经存在了,就一块删掉,如果不存在,就插进去。仍然需要考虑特殊情况的,比如相同mapping作异或操作等。源码:
1 static struct mapping *xor_mappings(struct mapping *a, struct mapping *b) 2 { 3 struct mapping *res; 4 struct keypair *k; 5 struct mapping_data *a_md = a->data; 6 struct mapping_data *b_md = b->data; 7 INT32 e; 8 ONERROR err; 9 10 /* First some special cases. */ 11 if (!a_md->size) return destructive_copy_mapping(b); /* 只是有可能会删除真数据 */ 12 if (!b_md->size) return destructive_copy_mapping(a); 13 if (a_md == b_md) return allocate_mapping(0); /* 数据一样,返回空mapping */ 14 15 /* Copy the largest mapping. 保持a<=b */ 16 if (a_md->size > b_md->size) { 17 struct mapping *tmp = a; 18 a = b; 19 b = tmp; 20 a_md = b_md; 21 b_md = b->data; 22 } 23 res = destructive_copy_mapping(b); /* 先拷贝个小的进去 */ 24 SET_ONERROR(err, do_free_mapping, res); 25 26 /* Add elements in a that aren't in b, and remove those that are. */ 27 NEW_MAPPING_LOOP(a_md) { 28 size_t h = k->hval & (b_md->hashsize - 1); 29 struct keypair *k2; 30 for (k2 = b_md->hash[h]; k2; k2 = k2->next) 31 { 32 if ((k2->hval == k->hval) && is_eq(&k2->ind, &k->ind)) /* 先比对哈希值,再比对真实数据 */ 33 { 34 break; 35 } 36 } 37 if (!k2) /* k2=0表示没有找到相同的 */ 38 { 39 mapping_insert(res, &k->ind, &k->val); 40 } 41 else 42 { 43 map_delete(res, &k2->ind); 44 } 45 b_md = b->data; 46 } 47 UNSET_ONERROR(err); 48 return res; 49 }
看看逻辑或运算,c=a|b 。这也是很普通的操作而已,实现起来也没有什么优化。思路是,1,若b比较大,那么先拷贝b到c,而对于a,逐个key判断是否存在于c,若在则不操作,否则插入。2,若a比较大,直接将a拷贝到c,再将b逐个插入到c,若key已存在,直接覆盖。
1 static struct mapping *or_mappings(struct mapping *a, struct mapping *b) 2 { 3 struct mapping *res; 4 struct keypair *k; 5 struct mapping_data *a_md = a->data; 6 struct mapping_data *b_md = b->data; 7 INT32 e; 8 ONERROR err; 9 10 /* First some special cases. */ 11 if (!a_md->size) return destructive_copy_mapping(b); 12 if (!b_md->size) return destructive_copy_mapping(a); 13 if (a_md == b_md) return destructive_copy_mapping(a); 14 15 if (a_md->size <= b_md->size) { /* a的数据比较少 */ 16 /* Copy the second mapping. */ 17 res = destructive_copy_mapping(b); 18 SET_ONERROR(err, do_free_mapping, res); 19 20 if (!b_md->hashsize) { 21 Pike_fatal("Invalid hashsize. "); 22 } 23 24 /* Add elements in a that aren't in b. */ 25 NEW_MAPPING_LOOP(a_md) { 26 size_t h = k->hval & (b_md->hashsize - 1); 27 struct keypair *k2; 28 for (k2 = b_md->hash[h]; k2; k2 = k2->next) { /* 如果b中已经存在的,a就不能再插进去了 */ 29 if ((k2->hval == k->hval) && is_eq(&k2->ind, &k->ind)) { 30 break; 31 } 32 } 33 if (!k2) { 34 mapping_insert(res, &k->ind, &k->val); 35 b_md = b->data; 36 } 37 } 38 UNSET_ONERROR(err); 39 } else { 40 /* Copy the first mapping. */ 41 res = destructive_copy_mapping(a); 42 SET_ONERROR(err, do_free_mapping, res); 43 44 /* Add all elements in b. */ 45 NEW_MAPPING_LOOP(b_md) { /* 直接将b插进去,如果a中已经存在key,则value会自动覆盖 */ 46 mapping_insert(res, &k->ind, &k->val); 47 } 48 UNSET_ONERROR(err); 49 } 50 return res; 51 }
and操作,即c=a+b,对于那些“key在a和b中皆存在,key-value属于b”的key-value于c中。源码的实现思路是,先将b拷贝到c,再将a中每个元素判断key是否存在于c中,若存在,则不操作,否则,删除。
1 static struct mapping *and_mappings(struct mapping *a, struct mapping *b) 2 { 3 struct mapping *res; 4 struct keypair *k; 5 struct mapping_data *a_md = a->data; 6 struct mapping_data *b_md = b->data; 7 INT32 e; 8 ONERROR err; 9 10 /* First some special cases. */ 11 if (!a_md->size || !b_md->size) return allocate_mapping(0); 12 if (a_md == b_md) return destructive_copy_mapping(a); 13 14 /* Copy the second mapping. */ 15 res = copy_mapping(b); /* 先拷贝了b */ 16 SET_ONERROR(err, do_free_mapping, res); 17 18 /* Remove elements in res that aren't in a. */ 19 NEW_MAPPING_LOOP(b_md) { /* 如果已经存在key,则不操作;否则删除已存在的key-value */ 20 size_t h = k->hval & (a_md->hashsize - 1); 21 struct keypair *k2; 22 for (k2 = a_md->hash[h]; k2; k2 = k2->next) { 23 if ((k2->hval == k->hval) && is_eq(&k2->ind, &k->ind)) { 24 break; 25 } 26 } 27 if (!k2) { 28 map_delete(res, &k->ind); 29 } 30 } 31 UNSET_ONERROR(err); 32 return res; 33 }
substract操作,即c=a-b,对于所有在b中出现的key,如果a中也存在,则删除,否则不操作。
1 static struct mapping *subtract_mappings(struct mapping *a, struct mapping *b) 2 { 3 struct mapping *res; 4 struct keypair *k; 5 struct mapping_data *a_md = a->data; 6 struct mapping_data *b_md = b->data; 7 INT32 e; 8 ONERROR err; 9 10 /* First some special cases. */ 11 if (!a_md->size || !b_md->size || !a_md->hashsize || !b_md->hashsize) { 12 return destructive_copy_mapping(a); 13 } 14 if (a_md == b_md) { 15 return allocate_mapping(0); 16 } 17 /* FIXME: The break-even point should probably be researched. */ 18 if (a_md->size < b_md->size) { 19 /* Add the elements in a that aren't in b. */ 20 res = allocate_mapping(a_md->size); 21 SET_ONERROR(err, do_free_mapping, res); 22 NEW_MAPPING_LOOP(a_md) { 23 size_t h = k->hval & (b_md->hashsize - 1); 24 struct keypair *k2; 25 for (k2 = b_md->hash[h]; k2; k2 = k2->next) { 26 if ((k2->hval == k->hval) && is_eq(&k2->ind, &k->ind)) { 27 break; 28 } 29 } 30 if (!k2) { 31 mapping_insert(res, &k->ind, &k->val); 32 } 33 } 34 } else { 35 /* Remove the elements in a that are in b. */ 36 res = destructive_copy_mapping(a); 37 SET_ONERROR(err, do_free_mapping, res); 38 NEW_MAPPING_LOOP(b_md) { 39 map_delete(res, &k->ind); 40 } 41 } 42 UNSET_ONERROR(err); 43 return res; 44 }
replace(mapping,old,new)函数,将mapping中所有value=old的替换成value=new。由于pike中使用了索引的概念,替换之后可能会有value需要清除了,如果它的引用次数=1的话。
1 PMOD_EXPORT void mapping_replace(struct mapping *m, struct svalue *from, struct svalue *to) 2 { 3 INT32 e; 4 struct keypair *k; 5 struct mapping_data *md; 6 7 #ifdef PIKE_DEBUG 8 if (m->data->refs <= 0) 9 Pike_fatal("Zero refs in mapping->data "); 10 #endif 11 12 md = m->data; 13 if (md->size) { 14 add_ref(md); /* 增加md的引用一次 */ 15 NEW_MAPPING_LOOP(md) { /* 扫描所有键值对 */ 16 if (is_eq(& k->val, from)) { /* 如果与from相同的话就申请内存并替换成to */ 17 PREPARE_FOR_DATA_CHANGE(); /* 为k申请新内存 */ 18 assign_svalue(& k->val, to); 19 md->val_types |= 1 << TYPEOF(*to); 20 } 21 } 22 free_mapping_data(md); /* 释放掉引用次数=1的value,不一定是真的释放 */ 23 } 24 25 #ifdef PIKE_DEBUG 26 if (d_flag > 1) check_mapping_type_fields(m); 27 #endif 28 }
mkmapping(array ind,array val)函数,创建一个mapping对象,并且以ind作为索引,val作为值来组成key-value初始化该对象。这里的实现是以ind的元素个数作为mapping的元素个数的,所以如果val的元素个数比ind要大的话,是会自动忽略后面的多余部分。还有一个点,就是初始化时,hashsize(也称之为桶的数量)应该是多少的问题,这里依靠MAP_SLOTS这个宏来计算的,这样的计算方式的优劣性未明~
1 #define MAP_SLOTS(X) ((X)?((X)+((X)>>4)+8):0) 2 PMOD_EXPORT struct mapping *mkmapping(struct array *ind, struct array *val) 3 { 4 struct mapping *m; 5 struct svalue *i, *v; 6 INT32 e; 7 8 #ifdef PIKE_DEBUG 9 if (ind->size != val->size) 10 Pike_fatal("mkmapping on different sized arrays. "); 11 #endif 12 13 m = allocate_mapping(MAP_SLOTS(ind->size)); /* 申请一个mapping,这里关系到hashsize的大小 */ 14 i = ITEM(ind); 15 v = ITEM(val); 16 for (e = 0; e < ind->size; e++) /* 逐个插入*/ 17 low_mapping_insert(m, i++, v++, 2); 18 19 return m; 20 }