2010-11-01 34 views
10

其編碼我有一種特殊的需要,最重要的擔憂是:算法:巨大的數量非常稀少位陣列,使用

  • 內存
  • 非常低內存佔用
  • 速度

這是我的「問題」:我需要在內存中存儲大量非常稀疏的位數組。這些bitset是「僅附加」,主要用於交叉點。巨大的,我的意思是高達20萬位陣列。

每個位集的範圍應在[0 ... 16 000 000]之間。

我進行了一些測試前與「唯一」包含一些實際的數據我有10個673比特串,得到了以下結果:

1% of the bit arrays ( 106 bit arrays) Hamming weight: at most  1 bit set 
    5% of the bit arrays ( 534 bit arrays) Hamming weight: at most  4 bits set 
10% of the bit arrays (1068 bit arrays) Hamming weight: at most  8 bits set 
15% of the bit arrays (1603 bit arrays) Hamming weight: at most 12 bits set 
20% of the bit arrays (2137 bit arrays) Hamming weight: at most 17 bits set 
25% of the bit arrays (2671 bit arrays) Hamming weight: at most 22 bits set 
30% of the bit arrays (3206 bit arrays) Hamming weight: at most 28 bits set 
35% of the bit arrays (3740 bit arrays) Hamming weight: at most 35 bits set 
40% of the bit arrays (4274 bit arrays) Hamming weight: at most 44 bits set 
45% of the bit arrays (4809 bit arrays) Hamming weight: at most 55 bits set 
50% of the bit arrays (5343 bit arrays) Hamming weight: at most 67 bits set 
55% of the bit arrays (5877 bit arrays) Hamming weight: at most 83 bits set 
60% of the bit arrays (6412 bit arrays) Hamming weight: at most 103 bits set 
65% of the bit arrays (6946 bit arrays) Hamming weight: at most 128 bits set 
70% of the bit arrays (7480 bit arrays) Hamming weight: at most 161 bits set 
75% of the bit arrays (8015 bit arrays) Hamming weight: at most 206 bits set 
80% of the bit arrays (8549 bit arrays) Hamming weight: at most 275 bits set 
85% of the bit arrays (9083 bit arrays) Hamming weight: at most 395 bits set 
90% of the bit arrays (9618 bit arrays) Hamming weight: at most 640 bits set 
95% of the bit arrays (10152 bit arrays) Hamming weight: at most 1453 bits set 
96% of the bit arrays (10259 bit arrays) Hamming weight: at most 1843 bits set 
97% of the bit arrays (10366 bit arrays) Hamming weight: at most 2601 bits set 
98% of the bit arrays (10473 bit arrays) Hamming weight: at most 3544 bits set 
99% of the bit arrays (10580 bit arrays) Hamming weight: at most 4992 bits set 
100% of the bit arrays (10687 bit arrays) Hamming weight: at most 53153 bits set 

看涉及的人數,我顯然需要使用壓縮這不是一個問題:它應該容易處理,看到位陣列是「僅附加」的。

打開的位陣列位有點分組,但不完全。所以你會傾向於在同一區域有幾個位(但通常不是一個接一個,因此RLE對於位不是很好)。

我的問題是使用什麼樣的壓縮?

現在我不知道我是應該在這裏還是在回答我自己的問題。

基本上我使用一個非常啞編碼想象「最壞情況」的場景:

  • 1比特:如果上,下面的5個比特確定需要多少位來計算「跳過」,如果關閉,優化:以下5位決定了字面上的字節數(即「開」或「關」),而不會跳過[只有在確定比其他表示更高效時纔會切換到此位當它開始運行時,它總是一個優化(size-wise)]

  • 5位:我們可以在下一個bi之前跳過多少位噸上

  • x比特:跳過

下面是一個例子:比特陣列具有3比特組,第一位是在3 098 137中,第二,在3 098 141和3中的第三098 143.

       +-- now we won't skip 
           | 
           |  +-- 3 because we need 3 bits to store "6" (from 3 098 138 to 3 098 143) 
           |  | +--- 3 098 141 is on 
    22 3 098 137    |  3 | +- 3 098 143 is on 
1 10110 1011110100011000011001 0 00011 000101 etc. 

首先打開告訴我們要跳過位。 5個位(總是5)告訴我們需要多少位來告訴我們將跳過多少位 22位告訴跳到3 098 137 一位關閉告訴我們現在不在跳過位 5 next bits(始終5)告訴我們有多少位會讀「原樣」 6位:關,關,關,開,關,就意味着3 098 141和3 098 143上 等

看到了驚人的這些位陣列的稀疏性,這似乎相當大的效率。所以使用這種編碼,我拿走了我的樣本數據,並計算了一個「最壞情況」的情況(我還沒有寫出算法,我寧願從這裏輸入一些數據):基本上我認爲不是隻有「大小優化」永遠不會踢和,也即5位總是被設置爲它們的最大值(24位),這當然是不可能發生的。

我這樣做只是擁有的「最壞最壞的」情況可能是什麼一個非常粗略的近似。

我感到很驚喜:

Worst case scenario: 

108 913 290 bits needed for the 10 687 very sparse bit arrays 
12.9 MB (13 295 KB) 

的數據是實際的數據,所有的數據是相似的,我知道,如果損壞程度嚴重,我可以存儲我的200個000位陣列,約240 MB,這很好。

我敢肯定,實際的編碼將涉及到比那少,但我還沒有實際寫入它,我只能(很容易)計算「最壞情況」這就是爲什麼我只能說明一。對於如何使這種尺寸效率更高的任何提示/想法(記住這些是超級稀疏的位數組,它們應該有成千上萬的數組,它們必須在內存中,並且它們應該是「僅追加「)?

關於我的「追加,只有」的情況下

基本上我有一個不斷增長的「遼闊」(範圍,但「無垠」是實際刑期按我的理解)和很多位陣列都有一些位集。當範圍從0到1 000 000時,所有的位數從0到1 000 000到。當範圍增長到1 000 001時,所有的位陣列也都在增長,一點一點地增長。但是,大多數這些位陣列將有一個「0」附加在其端部,而所述位陣列的約4至8將具有「1」所附在其端部。但是,我無法預測哪個位數組會附加0或1。所以我有很多位數組都有相同的大小,都非常稀疏(< 0.5%的位設置),並且隨着範圍增長「都在增長」(所以它們「總是以同樣的速度增長)。


Judy arrays很好。但幾年前我就讀過他們,那些東西「超出我的頭腦」。朱迪陣列是C-only 20KLOC庫,我絕對不會重新實現它。但他們很棒。

所以我想我需要補充,我想這一切都留比較簡單,這是不是牽強看到的特殊的「只追加」我非常稀疏比特串的財產。

+0

注意有關重新發明輪子的意見可發送到*的/ dev/null的*:如果只爲它背後的數學/挑戰我想自己實現這一點。無論如何,我會很驚訝地發現一個能夠處理內存中200 000個「僅追加」位陣列的輪子。但是如果你有一個,它後面的機制讓我感興趣:) – SyntaxT3rr0r 2010-11-01 23:28:12

+1

有理論對編碼密度的限制:對於N個元素的陣列,其中n個被設置,要編碼的最小位數將是-n * log2(n/N) - (Nn)* log(1-n/N)。對於其中設置了16M的53153的陣列,這將是514k位,並且對於4992位設置 - 65kits。讓你的記憶更接近這個極限,你必須選擇更復雜的編碼。 – Vovanium 2010-11-02 19:25:58

+1

@Vovanium,我認爲你爲你的理論極限排除了一些必要的上下文(比如,關於比特分佈設置的某種統計假設?) – comingstorm 2010-11-04 16:57:23

回答

2

即使他們不是你尋找什麼,這是值得一試Judy trees。 Judy是針對有序地圖的高度優化的庫,並且一個配置專門設計爲位集而不是地圖。我不認爲十字路口是本地優化的操作之一,儘管...

一般的想法是使用每層具有固定數量的地址位的樹,並利用每級的稀疏性。即使在最糟糕的情況下,這也會產生相當好的壓縮效果,同時還可以提高查詢性能。我相信一個交集操作會相對簡單並且可能非常快。

無論如何,從最好的東西偷走總是一個好主意!

+0

yup Judy數組非常棒,但老實說,它背後的數學對我來說有點複雜:)而AFAICT它只能作爲一個20KLOC C寫的庫: - /我肯定是重新發明*那個輪子:) – SyntaxT3rr0r 2010-11-01 23:55:18

+0

該死的,我的意思是,我絕對*不*重新發明*那個輪子:)顯然:) – SyntaxT3rr0r 2010-11-02 00:05:00

+0

沒有必要重新發明他們的輪子,但基本原理看起來就像是你正在尋找的東西:非常稀疏,並且很容易適應寫一個快速相交函數。 – comingstorm 2010-11-02 00:23:31

1

考慮到你要做一堆交叉測試,也許你應該嘗試並行存儲所有的位向量。一個稀疏的16M條目列表。該列表中的每個條目都包含200k輸入位向量中的哪一個在該位置具有'1'的列表。看起來您希望每個輸入矢量只設置大約5個位,或者總共1M個條目?以頂層和桶爲單位的稻草人鏈表實現,以及根本沒有交集的最壞情況(因此1M桶每個都有1個元素),你可以將其全部存儲在32MB中。

+0

不,我發佈的列表顯示它,例如:*「50%的位向量將具有[55和67位之間的集合「*。總共有超過1M的條目。對於200K位向量,我會說非常嚴重的是總共有1億比特集。 – SyntaxT3rr0r 2010-11-02 00:30:48

+0

我並沒有這樣看,但現在你提到了「另一種方式」,它保證了*「expanse」*(1600萬範圍)中的每一個將被使用幾次。按照您的說法,16M列表中的每個條目將設置大約4到8位。 – SyntaxT3rr0r 2010-11-02 00:32:36

+0

阿哈,我認爲這是一個總數,因此55k/10k = 5,我的錯誤。因此,沒有理由讓16M陣列稀疏,每個條目需要大約8個18位(2^18> 200k陣列)標識符的空間,因此需要288MB。與您的估計相似。 – 2010-11-02 00:39:42

3

您可能會使用二進制樹作爲位數組。假設你有[M..N]範圍的數組。 以這種方式存儲它:

爲[0 ... ram size]選擇一些數字編碼,如Fibonacci,Golomb或Rice代碼(在用實際數據分析程序後,您可以選擇最合適的表示形式)。

  1. 如果數組爲空(無位設置),將其存儲爲數字0。
  2. 如果數組已滿(都位設置),將其存儲爲數字1
  3. 在否則把它分解兩部分:[M ..(M + N)/ 2-1]中的A和[[(M + N)/2..N]中的B
  4. 遞歸地使用該算法生成P0和P1的表示。
  5. 獲取P0的長度(以位或其他單位長度可以爲整數)並將其存儲爲數字(如果長度可以是1,則可能需要加1,例如,將0存儲爲單個位0)。
  6. 存儲P0然後P1。

在這種情況下,如果限制是常見的,相交的聯合的操作是微不足道的遞歸:

路口:

  1. 如果陣列A爲空,存儲0。
  2. 如果陣列A是滿的,商店副本B
  3. 其他分割陣列,使兩個半部的交點,存儲前半部分的長度,然後是兩半。

該算法可能會處理比特(如果你需要它們是最緊湊的)和字節/字(如果比特操作太慢)。

此外,您還可以爲單位設置的數組添加特定的編碼,所有大小小於某個限制的數組(例如8個元素)可以降低遞歸級別。缺點是,如果沒有一些hack將元素添加到數組或從數組中刪除元素是複雜操作(與交集/並集操作一樣複雜)。

例如,具有單個0xAB位集的數組應該存儲在0的數組中。0xFF的作爲(僞碼):

爲空和滿陣列
0 1  2 3 4  5 6 7  8 9 10  11 12  13 14  15  16 
1, EMPTY, 13, 1, EMPTY, 9, 1, EMPTY, 5, 1, EMPTY, 1, EMPTY, FULL, EMPTY, EMPTY, EMPTY 
                | AA | AB | 
             |A8..A9| AA .. AB | 
             | A8   ..  AB |AC..AF| 
          |A0..A7| A8    ..    AF | 
         | A0     ..     AF |B0..BF| 
       |80..9F| A0      ..      BF | 
      | 80         ..      BF |C0..FF| 
| 0..7F| 80           ..       FF |  

空和滿是碼號中的元素的長度

FF你(應當與字節,位實際lengthts左右替換)不需要快速單一位檢查,您可以使用最簡單的方法: 只需使用代碼:斐波那契,米,golomb,levenshtein,elias等存儲設置位之間的距離或創建另一個。 請注意,爲了獲得最小代碼長度,應該使用代碼長度儘可能接近-log p/log 2的代碼,其中p代表該代碼的概率。你可以使用huffman代碼。

例如,使用Elias的伽馬代碼,所以陣列是這樣的:

0 1 0000 1 1 000 1 0 1 000000000000000000 1 000000000000000000 
2  5 1 4 2   19     18   (distance) 

應編碼爲:

010 00101 1 00100 010 000010011 000010010 
2 5 1 4 2  19  18  (distance code explained) 

而且大多緊湊爲具有均勻比特分配陣列將是算術編碼但它是非常CPU時間counsumpting。 Becaouse你必須讀寫這樣的數組,一點一點地沒有快速跳過。

+0

+1,很好的答案了。我不知道我會走哪條路線,但這確實給了思考的食物:) – SyntaxT3rr0r 2010-11-02 16:56:36

+0

謝謝。另外我可能會推薦看看如何製作各種聲音壓縮算法(MP2,AAC等)。當壓縮高頻譜時,它們處理稀疏陣列(如0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1,0)。 – Vovanium 2010-11-02 19:04:50

4

你沒有說你想用什麼編程語言。這聽起來像你不希望朱迪,因爲它是「只有C」......如果你使用C#,那麼你可以用我的Compact Patricia Trie來代替。幾乎是4500 LOC(評論)並且對Judy使用類似的想法,但由於.NET的限制,每個trie的大小和速度並不理想。它也未針對計算交點進行優化,但可以添加這樣的算法。關於CP Tries的文章並沒有強調這一點,但它可以比字典更緊湊地存儲集合(稀疏位陣列)(文章中的圖表顯示字典的大小和速度,而不是集合)。

最好的情況是密集的位集羣。佔用率爲50%(每隔一位設置一次),每個密鑰需要少於8位(每個整數少於4位)。 (校正:小於8位,而不是更多。)

如果只需要數據的近似表示,使用一個Bloom filter

順便說一句,你是什麼意思的「僅追加」?這是否意味着您只添加密鑰,或者您添加的每個密鑰是否大於您之前添加的密鑰?

更新:由於您只是添加較大的鍵,您應該設計一個特殊的算法,只爲您的情況。國際海事組織在設計自定義算法時,應儘可能簡單。所以這裏是我的想法,它假設不同位集的關鍵字是不相關的(因此,在不同位集之間壓縮數據沒有好處):

位集由32位插槽的排序陣列表示。由於它已排序,因此可以使用二分查找來查找關鍵字。每個插槽由24位「前綴」和8位「標誌」組成。每個插槽代表8個鍵的區域。在「標誌」告訴你,該地區的8個按鍵的是存在於位集,與「前綴」告訴你,我們正在談論哪個地區有關,通過指定位3至關鍵的26。例如,如果位集中的以下位爲「1」:

1, 3, 4, 1094, 8001, 8002, 8007, 8009 

...然後位集由4個時隙(16個字節)的陣列表示:

Prefix:  0, 136, 1000, 1001 
Flags: 0x15, 0x40, 0x86, 0x02 

第一時隙表示1,3,4(注意,比特1,3和4中的數字爲0x15設置);第二個時隙代表1094(136 * 8 + 6);第三個時隙代表8001,8002和8007;第四個插槽代表8009.這是否有意義?

我不知道這是否與您的想法一樣緊湊。但我認爲你會得到更快的查詢和更快的修改,而且實現起來相當容易。

+0

+1,很好的答案。對Patricia Trie還不太瞭解(除了我已經聽到的名字)之外,還會讀到。 Yup,* by *「append only」*我的意思是隨着「擴展」(範圍)的增長,一些位數組(通常是4到8)將會在位數組的末尾設置一個位。所以我從不「插入」位數組中間的任何位。因此,我認爲這是一個特殊的情況,它使事情變得更容易。 – SyntaxT3rr0r 2010-11-02 16:54:36

+0

我想通過「僅追加」我的意思是我只是添加鍵,而且鍵也總是大於我之前添加的鍵。 – SyntaxT3rr0r 2010-11-02 16:58:07

+0

我希望我可以給+1,你的文章看起來很棒,你的C#實現「CPT」也是如此。其實我以後的語言是*可能是Java,但是我可能需要一個簡單的方法將它移植到C#和Objective-C中...所以我寧願有一些相對容易的東西。但是你的緊湊型派翠西亞Trie看起來很棒。再一次,我的例子非常特別:我的大部分位陣列的每個位集都不超過0.5%,所以它真的是*超級稀疏*。 – SyntaxT3rr0r 2010-11-02 17:04:33

1

您可能會感興趣二元決策圖(BDD),更精確地說是零抑制二元決策圖(ZBDD)。

它們被用來以壓縮的方式表示集合。與其他壓縮表單不同,操作(例如設置交叉點或元素的插入 - 您的「僅追加」事物?)直接在壓縮表單上工作。

+0

我編輯了一下我的問題,以澄清「追加唯一的事情」。基本上,比特陣列不斷增長(最高達16 000 000比特),而且我總是隻修改它的末尾,因此直接在壓縮格式上工作很容易。 – SyntaxT3rr0r 2010-11-02 18:51:13

相關問題