Java源代码中的霍夫曼代码解码器编码器

时间:2015-03-04 09:48:39

标签: java performance parsing huffman-code

我想用Java创建一个快速的霍夫曼代码解码器,因此考虑了查找表。由于这些表占用内存并且我们使用Java代码来导航和访问表,因此可以轻松(或不)编写表达同一表的程序/方法。

这种方法的问题是,我不知道什么是最好的策略。我知道很多关于适合缓存和分支预测的内容。此外,开关盒实现意味着实际的ASM超出了我的范围。如果我有一个内存查找表(或它的层次结构),我将能够简单地跳入和跳出,但我为了我的目的而将该表放在缓存中。

因为我实际上走了一棵树,所以可以实现它,好像else语句需要一定数量的比较,但是对于每个比较,它需要额外的二进制操作。

因此存在以下选项:

  • 在内存查找表中使用的通用算法
  • if / else表示决策树
  • 如果/ else表示使用小的switch语句来查找正确的符号组(相同的位模式长度)(if语句较少,可能代码更多)。
  • 切换代码的​​语句表示

写作和基准测试非常棘手,因此任何初步想法都会很棒。

另外一个问题是位的顺序。最重要的位总是首先意味着它以相反的顺序存储。

  

如果你的树是A = 0,B = 10,C = 11来写BAC它实际上是01 + 0 + 11(加上意味着追加)。

所以实际上代码必须以相反的顺序编写。对于组使用if / else或switch方法它不会是一个问题,因为屏蔽掉这些位很简单,并且位的反转很简单,但它会失去从掩码中获取组内的索引的想法,因为反向位顺序添加和删除具有不同的含义,也不可能进行简单的查找。

反转位是一项代价高昂的操作(我使用4位查找表)并没有超过二进制操作的性能。

但是,在移动中反转这些比特更适合这一点,并且每位需要四次操作(向上移位,屏蔽,添加并向下移位输入)。由于我先前读取了所有这些操作,因此它们可能只需要几个周期。

这样我可以使用switch,sub和if来找到正确的符号组,也可以返回那些。

所以最后我需要建议。由于我的代码是语言处理的全局代码,因此它们可以是硬连线的(即在源代码中)。

我想知道像ANTRL这样的解析器生成器用来表达那些决定。因为他们也接缝切换或if / else基于输入符号,它可能会给我一个线索。

[更新]

我发现了一种避免反向位问题的简化,但仍然增加了每组的成本。所以我最终按照遍历的组的顺序写入位。因此,我不需要每位进行四次修改,而是每组(不同的位长)。

对于我们拥有的每个小组: 1.第一个元素的值,大小(以及该组中最后一个元素的值。

因此,对于每个组,算法如下所示: 1.读取mbits并与当前读取值组合。 2.将该值与该组的最后一个值进行比较,如果不在其外部,则该值小于该组中的值。 - >读下一个 3.如果它在组内,则可以访问值数组或使用switch语句。

这是完全通用的,可以在没有循环的情况下使用,从而提高效率。此外,如果检测到该组,则代码的位长度是已知的,并且可以从源中消耗这些位,因为代码看起来很远(从流中读取)。

[更新2]

要访问实际值,可以使用按组分组的单个大数组元素。由于组的可支持性降低,因此很可能一个重要部分适合L2或L1缓存,从而加快了访问速度。

或者使用switch语句。

[更新3]

根据交换机的情况,编译器会生成一个tableswitch或一个查找开关。查找开关具有O(log n)的复杂度并存储密钥,jmp偏移对,这不是优选的。因此,检查组更适合if / else。

tableswitch本身只使用一个跳转偏移表,它只需要减去,比较,访问,jmp到达目的地,而不是必须在常量上执行返回值。

因此表访问看起来更有前景。另外,为了避免不必要的跳转,每个组可能包含访问和返回组符号表的逻辑。将所有内容存储在一个大表中是有希望的,因为它可能是每个符号的int或short并且我的代码通常最多只有1000到4000个符号使它实际上很短。

我将检查1 - 模式是否会让我有机会以更好的方式存储和访问掩码,允许二进制搜索正确的组而不是在O(n)中前进,甚至可能在任何时候都避免任何移位操作处理。

2 个答案:

答案 0 :(得分:4)

我无法理解你在(长)问题中所写的大部分内容,但有一个简单的方法。

我们将从一张桌子开始。让我们说你最长的霍夫曼代码是15位。 (事实上​​,deflate将其霍夫曼代码的大小限制为15位。)然后构造一个包含32768个条目的表,其中每个条目是下一个代码中的位数,以及该代码的符号。对于小于15位的代码,表中有多个条目用于相同的代码。例如。如果符号' C'的代码是10010110(7位),则表xxxxxxxx10010110的所有索引具有相同的内容。这些条目都有{7,' C'}。

然后从流中获得15位,并查找表中的下一个代码。您从该表条目中删除位数,并使用结果符号。现在,您需要从流中获取尽可能多的位数,然后重复。因此,如果您使用7位,那么再获得8位以返回15并查找下一个代码。

下一个细微之处在于,如果你的霍夫曼代码经常发生变化,你可能会花费更多时间为每个新的霍夫曼代码填充那个大表,而不是实际解码。为避免这种情况,您可以创建一个两级表,例如,对于代码的第一部分,它具有9位查找(512个条目)。如果代码是9位或更少,则按上述步骤操作。这将是最常见的情况,因为更短的代码更频繁(这是霍夫曼编码的全部要点)。如果表条目表示代码中有10位或更多位(并且您还不知道还有多少位),则使用前9位并转到初始9位的第二级表由第一个表中的条目指向,其中包含剩余六位(64个条目)的条目。这解析了代码的其余部分,因此告诉您要消耗多少比特以及符号是什么。这种方法可以大大减少填充表格所花费的时间,并且由于短代码更常见,因此非常快。这是inflatezlib使用的方法。

答案 1 :(得分:0)

最后它非常简单。我现在支持几乎所有解决方案。可以测试每个符号组(相同的位长),使用查找表(10bit + 10bit + 10bit(只是10bit的表,symbolscount + 1是对这些talbes的引用))并生成java(如果需要javascript但是目前我使用GWT翻译它)。

我甚至使用长读取和移位操作来减少对二进制信息的访问。这样代码变得更有效率,因为我只支持最大位大小(20位(因此表的一个表),它产生2 ^ 20个符号,因此最多只有一百万个。)

对于排序我只使用移位操作使用发生器作为位掩码,并且不需要反转位顺序等。

表查找也可以用Java表示,将表存储为数组数组(有趣的是java文件有多大,没有编译器可以抱怨)。

此外,我发现有趣的是,由于比较表达了一种排序(我猜的是半阶),因此可以对符号进行排序,而不是映射映射比较索引的符号。通过比较两个索引,可以简单地对代码流进行排序而不会触及太多。通过同时存储第一个或前两个比较索引(16或32位),可以使用相同的霍夫曼代码对列表进行有效排序,从而对压缩字符串进行二进制排序,这使其成为压缩特定语言字符串的理想选择。