读过《编程珠玑》(<Programming Pearls>)的人应该还对开篇的Case记忆犹新,大概的场景是:redis
做者的一位在电话公司工做的朋友想要统计一段时间内不一样的电话号码的个数,电话号码的数量很大,当时的内存很小,因此不能把全部的电话号码所有放到内存来去重统计,他的朋友很苦恼。算法
做者聪明的想到了用bit数组来解决问题,每一个电话号码能够映射为bit数组的index,bit数组初始状态全部位为0,全部电话号码逐一处理:将bit数组对应位置为1,处理完以后统计bit数组中有多少个1便可。编程
示例:[0,1,0,0,0,1,0,...] 这个bit数组表示2和5存在数组
不得不说这种想法很是精妙,即减小了内存占用(8位电话号码若是所有放到内存须要381M(每一个电话号码存成Integer占4Byte计算),而使用bit数组只须要11M),并且只须要两次循环就能够获得结果;app
这是一个基数计数的问题,Cardinality:estimating the number of distinct elements.ui
上边提到的方式是使用bitmap,思路是将dataset中的每个element映射到一个bit位,不容许冲突,所须要的内存空间大概为基数*1bit(上例中是100,000,000bit),而且计数精准;google
可是当基数很是大时,即便bitmap内存也放不下!blog
好消息是若是不要求计数精准(容许必定范围内的偏差),能够采用几率估算算法:内存
其中:n是估算值,m是bitmap大小,Vn是bitmap中0出现的比率,好比0.1;element
使用固定大小的hashtable来存放dataset,能够想见:
因此能够根据hashtable中key被占用的状况来估算dataset的基数,这里主要用到了对数曲线的特性(0<x<1这一段):
初始化一个bitmap,全部位为0,dataset的每个element都hash到bitmap的一个bit位,hash以后将对应的bit位置为1,hash容许冲突,最后根据bitmap中0的数量和bitmap大小由公式来估算基数;
注意:虽然LC用的也是bitmap,可是相比原始的bitmap算法,LC的bitmap大小能够比基数小不少,由于LC的映射容许冲突,另外能够设置bitmap大小来决定偏差的大小;
推导过程详见参考
参考:A linear-time probabilistic counting algorithm for database applications
其中:m是桶的数量,M为桶的集合,k是用于分桶的位数,ρ为bit数组中第一个为1的下标即index,E是估算值;
来看抛硬币的过程,抛硬币的过程是伯努利Bernoulli过程,每次的结果要么是0,要么是1,而且几率均为1/2,假设一次抛硬币game定义为抛到1为止,可能第一次就抛到1(P=0.5),也可能前边抛了i次0最后才抛到1(P=1-0.5^i);抛硬币game玩的次数越多,越容易出现前边不少次都是0的状况,这是由于开头连续出现的0的个数越多,出现几率越小,须要尝试伯努利过程的次数就越多,因此能够利用几率根据结果(开头出现0的个数,即i)来反推出条件(game玩了多少次,即n=2^i,这个也很容易理解,好比彩票的中奖几率是1/10000,即平均买10000张彩票才能中一次奖,反过来讲,若是有人中了一次奖,他很大几率上应该买了10000张彩票);
可是因为随机性的存在致使偏差较大,因此经过将dataset分为m份,每份单独统计,最后取算数平均值的方式来下降随机性从而减少偏差;
dataset的每个element先映射到一个bit数组,好比32位bit数组,将这个bit数组的1到k位的值做为桶的bucket_index(即第几个桶),将k+1到32位中第一个为1的index做为value放到桶中,若是桶里已经有value,桶会保存一个最大的value,数据集元素所有映射完以后,将全部桶的value取算数平均值,根据n=2^i,这样能够获得每一个桶内的基数,再乘以m能够获得整个dataset的基数,公式最前边的α是修正参数;
好比k=2,则m=2^k=4,即4个桶,dataset中一个element映射的bit数组为[1,0,0,0,0,1,0,...],取前两位[1,0]对应的值是2,即第2个桶,取第3位以后的数组[0,0,0,1,0,...]可见第一个位1的index是3,将3放到桶2中,以此类推;
推导过程详见参考
参考:Loglog Counting of Large Cardinalities
同LLC,只有一点不一样:取均值的时候不使用算数平均数而改用调和平均数
推导过程详见参考
参考:HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm
在大量实践中根据各个参数和结果的状况进行调优,综合使用HLLC和LC等算法
目前HLLC在不少开源组件中都有应用,好比redis、druid等
其余:
https://research.neustar.biz/2012/10/25/sketch-of-the-day-hyperloglog-cornerstone-of-a-big-data-infrastructure/