22.PHP內核探索:PHP哈希算法設計

Hash Table是PHP的核心,這話一點都不過分。PHP的數組、關聯數組、對象屬性、函數表、符號表等等都是用HashTable來做爲容器的。

PHP的HashTable採用的拉鍊法來解決衝突,這個自不用多說,我今天主要關注的就是PHP的Hash算法,和這個算法本身透露出來的一些思想。

PHP的Hash採用的是目前最爲普遍的DJBX33A (Daniel J. Bernstein, Times 33 with Addition),這個算法被廣泛運用與多個軟件項目,Apache、Perl和Berkeley DB等。對於字符串而言這是目前所知道的最好的哈希算法,原因在於該算法的速度非常快,而且分類非常好(衝突小,分佈均勻)。

算法的核心思想就是:

hash(i) = hash(i-1) * 33 + str[i]

在zend_hash.h中,我們可以找到在PHP中的這個算法:

static inline ulong zend_inline_hash_func(char *arKey, uint nKeyLength)
{
    register ulong hash = 5381;
 
    /* variant with the hash unrolled eight times */
	for (; nKeyLength >= 8; nKeyLength -= 8) {
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
        hash = ((hash << 5) + hash) + *arKey++;
    }
    switch (nKeyLength) {
        case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */
        case 1: hash = ((hash << 5) + hash) + *arKey++; break;
        case 0: break;
EMPTY_SWITCH_DEFAULT_CASE()
    }
    return hash;
}

相比在Apache和Perl中直接採用的經典Times 33算法:

hashing function used in Perl 5.005:
# Return the hashed value of a string: $hash = perlhash("key")
# (Defined by the PERL_HASH macro in hv.h)
sub perlhash
{
    $hash = 0;
    foreach (split //, shift) {
        $hash = $hash*33 + ord($_);
    }
    return $hash;
}

在PHP的hash算法中,我們可以看出很處細緻的不同。首先,最不一樣的就是,PHP中並沒有使用直接乘33,而是採用了:

hash << 5 + hash

這樣當然會比用乘快了。

然後,特別要主意的就是使用的unrolled,我前幾天看過一篇文章講Discuz的緩存機制,其中就有一條說是Discuz會根據帖子的熱度不同採用不同的緩存策略,根據用戶習慣,而只緩存帖子的第一頁(因爲很少有人會翻帖子)。

於此類似的思想,PHP鼓勵8位一下的字符索引,他以8爲單位使用unrolled來提高效率,這不得不說也是個很細節的,很細緻的地方。

另外還有inline,register變量 … 可以看出PHP的開發者在hash的優化上也是煞費苦心。

最後就是,hash的初始值設置成了5381,相比在Apache中的times算法和Perl中的Hash算法(都採用初始hash爲0),爲什麼選5381呢?具體的原因我也不知道,但是我發現了5381的一些特性:

Magic Constant 5381:
  1. odd number
  2. prime number
  3. deficient number
  4. 001/010/100/000/101 b

看了這些,我有理由相信這個初始值的選定能提供更好的分類。

至於說,爲什麼是Times 33而不是Times 其他數字,在PHP Hash算法的註釋中也有一些說明,希望對有興趣的同學有用:

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 <[email protected]>


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