我正在编写一个压缩库作为一个小小的项目,我已经足够了(我的库可以提取任何标准的gzip文件,以及生成兼容的(但肯定不是最佳的)gzip输出)它是是时候找出一个有意义的块终止策略了。目前,我只是在每32k输入(LZ77窗口大小)之后关闭块,因为它是通用且快速实现的 - 现在我回过头来尝试实际提高压缩效率。
Deflate spec只能这样说:“当压缩器确定开始使用新树的新块时,或者当块大小填满压缩器的块缓冲区时,压缩器会终止一个块” ,这不是那么有用。
我通过SharpZipLib代码进行了排序(因为我认为这将是一个可读的开源实现),并发现它每16k字符输出终止一个块,忽略输入。这很容易实现,但似乎必须有一些更具针对性的方法,特别是考虑到规范中的语言“确定用新树开始一个新块将是有用的”。
那么有没有人对新战略或现有战略的例子有任何想法?
提前致谢!
答案 0 :(得分:2)
建议你去。
预测前瞻性的缓冲区具有足够大的缓冲区,以指示优异的压缩值,值得改变。
这会改变流式传输行为(在输出发生之前需要输入更多数据)并且使诸如flush之类的操作显着复杂化。它在压缩桩中也是一个相当大的额外负荷。
在一般情况下,可以确保通过在可以启动新块的每个点处分支来产生最佳输出,使两个分支在必要时递归直到所有路径都被采用。具有嵌套行为的路径获胜。这对于非平凡的输入大小来说是不可行的,因为选择何时开始新的块是如此开放。
简单地将其限制为至少8K输出文字,但是阻止块中超过32K的文字将导致尝试推测算法的相对容易的基础。拨打8K子块。
最简单的是(伪代码):
create empty sub block called definite
create empty sub block called specChange
create empty sub block called specKeep
target = definite
While (incomingData)
{
compress data into target(s)
if (definite.length % SUB_BLOCK_SIZ) == 0)
{
if (targets is definite)
{
targets becomes
specChange assuming new block
specKeep assuming same block as definite
}
else
{
if (compression specChange - OVERHEAD better than specKeep)
{
flush definite as a block.
definite = specChange
specKeep,specChange = empty
// target remains specKeep,specChange as before
but update the meta data associated with specChange to be fresh
}
else
{
definite += specKeep
specKeep,specChange = empty
// again update the block meta data
if (definite is MAX_BLOCK_SIZE)
{
flush definite
target becomes definite
}
}
}
}
}
take best of specChange/specKeep if non empty and append to definite
flush definite.
OVERHEAD是一个不变的因素,可以解释切换块的成本
这很粗糙,可能会有所改善,但如果没有别的话,它就是分析的开始。检查代码以获取有关导致切换的原因的信息,使用该代码来确定改变可能有益的良好启发式(可能是压缩比率显着下降)。
这可能导致specChange的构建仅在启发式认为合理时才进行。如果启发式结果是一个强有力的指标,那么你就可以消除投机性质,并且无论如何都只是决定交换点。
答案 1 :(得分:0)
嗯,我喜欢一些启发式分析的想法,试图提出一些“规则”,以便在结束块时可能是有益的。今晚我会研究你建议的方法,看看我能用它做些什么。
与此同时,我发现为了在这个问题上做出完全明智的选择,我需要更好地了解块大小决策的利弊。真的很快我得到了较小的块允许你有一个可能更好的目标符号字母表 - 代价是更频繁地定义树的开销增加。较大的块使用比例效率来对抗它们更通用的符号字母表(只有一棵树可以存储和解码大量编码数据)。
在我的脑海中,不清楚是否相对分布的长度代码与长度,距离代码是否会对最佳块大小产生特定影响。不过要考虑好的食物。