程序员lua

[lua source code] luaS_hash

2019-01-09  本文已影响26人  ffl

上一节对global_State做了子模块划分,回顾一下global_State整理后的代码:

typedef struct global_State {
  // Version
  const lua_Number *version;      /* pointer to version number */

  // Hash
  unsigned int seed;              /* randomized seed for hashes */

  // Global Registry
  TValue l_registry;

  // Global String table
  stringtable strt;               /* hash table for strings */
  Mbuffer buff;                   /* temporary buffer for string concatenation */

  // Global Meta table
  TString *tmname[TM_N];          /* array with tag-method names */
  struct Table *mt[LUA_NUMTAGS];  /* metatables for basic types */

  // Global Thread list
  struct lua_State *mainthread;
  struct lua_State *twups;        /* list of threads with open upvalues */
  
  // Memory Allocator
  lua_Alloc frealloc;             /* function to reallocate memory */
  void *ud;                       /* auxiliary data to 'frealloc' */

  // GC Info
  GCInfo *gcinfo;

  // Error Recover
  lua_CFunction panic;            /* to be called in unprotected errors */
}

加深一下印象,也就是说global_State内部包含了下面这些重要部分:

其中,Version指向了版本指针,通常我们发布程序版本号都是外置的,Lua把版本号内置在全局状态里,使得可以在运行时动态判断自己的版本号。

而,Hash部分的seed则是用在散列表里面计算哈希时的种子,在Lua的代码里可以找到这段代码:

//lstring.c
unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) {
  unsigned int h = seed ^ cast_uint(l);
  size_t step = (l >> LUAI_HASHLIMIT) + 1;
  for (; l >= step; l -= step)
    h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));
  return h;
}

第1眼看上去luaS_hash的代码没什么道理可言,其实一步步展开还是能理解的。首先,哈希函数有两类:

luaS_hash即属于非加密用的哈希函数,在散列表里的主要作用是:

可见,它本质上是一种伪随机数生成器,而我们知道伪随机数生成器最古典的就是线性同余方法(wiki:Linear congruential generator(LCG)),它的算法如下:

N_{j+1}===(A*N_j+B)(mod M)

LCG算出来的伪随机数序列在模M之后就是一个周期整数序列,周期的大小决定了碰撞的概率。LCG的周期最大是M,但大部分情况都会少于M,如果要让LCG达到最大周期,应该要符合以下Hull–Dobell Theorem条件:

  1. B,M互质
  2. M的所有质因子都能整除A-1
  3. 若M是4的倍数,A-1也是。
  4. A,B,N_0都比M小
  5. A,B都是正整数

LCG本身只是伪随机数生成器,需要满足均匀分布以减少碰撞才能在散列表里面使用。因此一个非密码学使用的哈希函数是否足够好(good hash function),就要看它是否足够均匀分布。

先看一个常见的Hash函数的实现,叫做DJBX33A (Daniel J. Bernstein, Times 33 with Addition):

unsigned int DJBHash(char* str, unsigned int len){
   unsigned int hash = 5381;
   unsigned int i    = 0;

   for(i = 0; i < len; str++, i++) {   
      hash = ((hash << 5) + hash) + (*str);
   }   

   return hash;
}

在for循环里做的hash = ((hash << 5) + hash) + (*str);实际上就是hash = hash*33+str[i],就是LCG递归算法的一个中间步骤。对比一下公式,可以看到:

可以看到A,B,M满足如下的几个性质:

因此DJBX33A这个哈希函数具有很好的最大周期性,从而尽可能减少碰撞。但这只解释了一半原因,另一半原因是:

上面这段解释来自参考资料[2]的评论,而参考资料[3]的评论下则有一段对DJB里的这几个魔数的来源做了另一番解释:直接暴力计算对比,you can you up!

/*
* DJBX33A (Daniel J. Bernstein, Times 33 with Addition)
*
* This is Daniel J. Bernstein's popular `times 33' hash function as
* posted by him years ago on comp.lang.c. It basically uses a function
* like ``hash(i) = hash(i-1) * 33 + str[i]''. This is one of the best
* known hash functions for strings. Because it is both computed very
* fast and distributes very well.
*
* The magic of number 33, i.e. why it works better than many other
* constants, prime or not, has never been adequately explained by
* anyone. So I try an explanation: if one experimentally tests all
* multipliers between 1 and 256 (as RSE did now) one detects that even
* numbers are not useable at all. The remaining 128 odd numbers
* (except for the number 1) work more or less all equally well. They
* all distribute in an acceptable way and this way fill a hash table
* with an average percent of approx. 86%.
*
* If one compares the Chi^2 values of the variants, the number 33 not
* even has the best value. But the number 33 and a few other equally
* good numbers like 17, 31, 63, 127 and 129 have nevertheless a great
* advantage to the remaining numbers in the large set of possible
* multipliers: their multiply operation can be replaced by a faster
* operation based on just one shift plus either a single addition
* or subtraction operation. And because a hash function has to both
* distribute good _and_ has to be very fast to compute, those few
* numbers should be preferred and seems to be the reason why Daniel J.
* Bernstein also preferred it.
*
*
* -- Ralf S. Engelschall <rse@engelschall.com>
*/

除了DJBX33A哈希函数外,下面两个著名的哈希函数也是类似的做法:

回到luaS_hash,与DJBX33A哈希函数是类似的,其中:

可以看到seed正是使用global_State里的seed,下面的代码验证了这点:

static TString *internshrstr (lua_State *L, const char *str, size_t l) {
  TString *ts;
  global_State *g = G(L);
  stringtable *tb = &g->strt;
  unsigned int h = luaS_hash(str, l, g->seed);
  TString **list = &tb->hash[lmod(h, tb->size)];
  ...
}

而global_State->seed的初始化如下:

LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  ...
  g->seed = luai_makeseed(L);
  ...
}

进一步,我们看下luai_makeseed(L);的代码:

static unsigned int luai_makeseed (lua_State *L) {
  char buff[3 * sizeof(size_t)];
  unsigned int h = cast_uint(time(NULL));
  int p = 0;
  addbuff(buff, p, L);  /* heap variable */
  addbuff(buff, p, &h);  /* local variable */
  addbuff(buff, p, &lua_newstate);  /* public function */
  lua_assert(p == sizeof(buff));
  return luaS_hash(buff, p, h);
}

这就是随机种子初始化的地方,可以看到最后一句调用了luaS_hash来递归的生成种子本身,而buff,p,h就是初始值,其中h就是“生成随机种子的种子”,分拆下:

待续

至此,我们分解清楚了global_State->seed的作用,以及luaS_hash函数背后的原理。下一次,我们会继续探索global_State-> l_registry。对技术背后原理的好奇,是前进的最大动力。

[1] https://en.wikipedia.org/wiki/Linear_congruential_generator
[2] https://stackoverflow.com/questions/1579721/why-are-5381-and-33-so-important-in-the-djb2-algorithm
[3] https://stackoverflow.com/questions/10696223/reason-for-5381-number-in-djb-hash-function/13809282#13809282
[4] https://en.wikipedia.org/wiki/List_of_hash_functions#Non-cryptographic_hash_functions

上一篇 下一篇

猜你喜欢

热点阅读