2010-11-01 38 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

相关问题