Skip to content

lifenjoiner/Compare-Hash-Functions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

实例:比较优选字符串 HashTable 的 Hash 函数

背景

Hash,是摘要特征值,用于替代繁长的原始数据,期望尽量独特,但是难免会有 collisions。

HashTable,是把大量的 Hash 值,分类到更短的序数化队列,加速检索的组织方式;一维转二维,像图书馆的分类上架,分类内元素量少更好管理;一个类别内再比较是要原始数据完全匹配的,元素可以无序,也可以有相同 Hash 值。

这是 改进 dnsforwarder 的 HashTable 时所做的工作。网上能够检索到的 Hash 算法比较,有具体数据、实例性质的很少。公布出来,希望对有疑问者有帮助。毕竟,一个 Hash 函数,计算大量可变的输入数据之后的结果到底怎样,没有具体数据,很难靠想象评价。

准备

首先,准备工作,收集一些 Hash 函数。这里是 c 语言的。

GeneralHashFunctions 下11个:

APHash
BKDRHash
BPHash
DEKHash
DJBHash
ELFHash
FNVHash
JSHash
PJWHash
RSHash
SDBMHash

Hashes-Ex.c 内额外4个变形:

BKDR1313
BKDR13131
BKDR131313
ELFHash2

第二步,编写计算 Hash 的工具:HashesHashes-Ex(后来拓展的,也可合并为一个)。编译,这里用的tcc

tcc -I GeneralHashFunctions Hashes.c GeneralHashFunctions\GeneralHashFunctions.c
tcc Hashes-Ex.c

编译好的:下载

第三步,准备样品数据。 这里是一些常用 Host 文件合集。用 Excel 即可:筛选删除注释,分列保留 host,再去重得到规整的清单。237058条。保存为 data/DNS-fw-hosts.txt

第四步,计算 Hashes。 创建data\hashes目录,在hash-tools目录,运行:

Hashes.exe ..\data\DNS-fw-hosts.txt >..\data\hashes\DNS-fw-hosts-hashes.txt
Hashes-Ex.exe ..\data\DNS-fw-hosts.txt >..\data\hashes\DNS-fw-hosts-hashes-ex.txt

数据文件:下载

比较方案

比什么?最直接的,比较全部遍历 HashTable 元素的总操作次数。

以下包含,从直觉到调用专业统计工具,两种方案。

方案一:跟着直觉走,用 Excel,也探索一下需要设定什么评价指标。

这是普通用户即可获得的工具,写几个通用公式,拖拽填充一下,就能处理不少数据:

  1. 创建文件夹method-excel

  2. data\hashes\DNS-fw-hosts-hashes.txt导入 Excel 文件,保存为method-excel\Hashes.xlsx。注意:要设定第二列往后的所有 Hash 列格式为文本,避免个别mEn格式的被识别为科学计数。

    hashes

  3. 构建 HashTable 的数据排布。即用 HashTable 的大小,取余数,索引化。用 Bucket 代指。

    这里选定了这些大小:0xFFFFFFFF, 1000003, 100003, 10007, 28672, 57344, 28669, 57347。

    0xFFFFFFFF 时, 相当于衡量 Hash 函数本身的结果特异性,不需要取余;其它的取余类似,以 1000003 为例。参考 method-excel\HT1000003.xlsx的 A:L 列。单元格 A1 为 HashTable 的大小;B2 的公式为:=MOD(HEX2DEC([Hashes.xlsx]hashes!B2), $A$1),向右填充到 L2,B2:L2 向下填充到237058+1行。

    1000003-index

  4. 计算总操作次数。

    这里增加了两个辅助区块:M:W 和 X:AH。

    M1,APHash Count表示每个 Bucket 内的元素个数。M2 输入公式 =CountIf(B$2:B$237059, B2),填充到 M2:W237059 可得全部。但是,这里会暴露出 Excel 的第一个不足:处理巨量数据时的性能不足,填充一列,计算完成就要高负荷运算几十分钟。 于是,做了辅助工具 countif.js,提高速度。用法:把 B 列保存为method-excel\2_HT1000003\APHash.txt,运行cscript countif.js method-excel\2_HT1000003\APHash.txt,1秒钟得结果,再把计数结果贴到 M 列对应的位置。 其它列,类似。

    X1,APHash Rounds/N表示每个元素需要的平均操作次数,用于计算总操作次数。X2,公式=(M2+1)/2,填充到 X2:X237059 得全部,再将 X3:X237059 复制粘贴只保留值,减少公式自动更新也减小文件大小。

    hashes-count-and-round-formula

    将所有选定 HashTable 大小的情形都耐心地计算出来,分别保存在不同的文件。

    如果,保存在同一个文件的不同 Sheet 里,你会发现 Excel 的第二个不足:大文件保存缓慢。前面的操作,数据量在、次数多,需要及时保存,防止卡死也便于分次完成。但是,当文件 xlsx 大于 100M 的时候,内部的 xml 文件会超过 1G,存取很慢,频繁地保存就是频繁地工作在极限状态,其在这方面没有优化,不是专业的数据库。

    这一步是最耗时的,又相似容易混淆,分次完成即可。难处已经被化解开了。最终,可得到如下的数据文件

    hashes-counts-and-rounds

  5. 统计评估。公式和数据,参考 method-excel.7zstatistics.xlsx 文件。

    statistics

    平均元素数:总元素数/Bucket数,进位取整。

    独特率:Hash 唯一的比率。

    冲突个数:Hash 不唯一的个数。

    组冲突最多个数:最多元素数 Bucket 的元素数。沿用 0xFFFFFFFF 时的叫法,其它的不一定是 Hash 相同。

    满载组数:理想(平均)分布时,满载的 Bucket 的个数。用于计算理想状态下的总操作次数。

    遍历差:实际总操作次数 - 理想状态下的总操作次数。

    超过平均计数:实际分布中多于平均元素数的 Bucket 包含的元素数。

    超过平均占比:超过平均计数/总元素数。

到这里,方案已经走通了。

把拓展的data\hashes\DNS-fw-hosts-hashes-ex.txt用相同的方法处理。保存在 method-excel\ex下。数据下载

hashes-counts-and-rounds-ex

最后,再做个直观化展示。

statistics.xlsxstatistics-ex.xlsx 的值复制到新文件,格式化。 参考method-excel.7zStringHashTableCompare-PrettyReport.xlsx。 技巧是:把3-Color Scale条件格式化的 Midpoint 设置成单元格的值(U 列),方便动态调整。

得如下图:

DomainHashesCompare2

颜色越偏向红色越差,越偏向绿色越好。

BPHash 最差首先排除;再排除 ELFHash2;然后排除 ELFHash 和 PJWHash;最后排除 DEKHash,剩下的基本在同一水平。

这里有一个奇异的现象:ELFHash 和 PJWHash 的数据竟然是一样的!检查对应的 Hash,加一列公式比较可知完全一样。Hashes程序也并没有写错。这两个算法的结果是一样的!

方案二:使用专业数据处理统计工具。也校验一下方案一是否有算错。

这里用 python 调用pandas库,写了 hashtable-cmp.py

usage: hashtable-cmp <hashes-file> [write-intermediate-data]

处理 data\hashes\DNS-fw-hosts-hashes.txtdata\hashes\DNS-fw-hosts-hashes-ex.txt,得对应的 csv 文件,移动到文件夹method-pandas,合并得文件 method-pandas\DNS-fw-hosts-hashes.statistics.xlsx。按 Slots 和 Type 排序即可比较 Loops (实际总操作次数)。

为了得到方案一的遍历差(P 列):

=[@Loops] - ([@Entries]+1)*[@Entries]/2*[@IdealLoadSlots] - [@Entries]*([@Entries]-1)/2*([@Slots]-[@IdealLoadSlots])

又增加了辅助列 O:

=[@Amount] - ([@Entries] - 1)*[@Slots]

理想分布是:一部分是平均元素数Entries), 另一部分相比少一个元素;即所有Slots都有至少[@Entries] - 1个元素,其中一部分会再多一个,这部分多的数量就是等于平均元素数Slots个数。

结论

最终,可以验证方案一的结果是正确无误的!你也可以重新采样,评估,选择合适的 Hash 函数。