讀過《編程珠璣》(<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/