【问题标题】:How to efficiently predict if data is compressible如何有效地预测数据是否可压缩
【发布时间】:2011-08-11 13:48:45
【问题描述】:

我想编写一个存储后端来存储更大的数据块。数据可以是任何东西,但主要是二进制文件(图像、pdf、jar 文件)或文本文件(xml、jsp、js、html、java...)。我发现大部分数据已经被压缩了。如果全部压缩,大约可以节省 15% 的磁盘空间。

我正在寻找一种最有效的算法,该算法可以高概率地预测可以压缩或不压缩数据块(比如 128 KB)(无损压缩),而无需尽可能查看所有数据。

压缩算法将是 LZF、Deflate 或类似的算法(可能是 Google Snappy)。所以预测数据是否可压缩应该比压缩数据本身要快得多,并且使用更少的内存。

我已经知道的算法:

  • 尝试压缩数据的一个子集,比如说 128 字节(这有点慢)

  • 计算 128 字节的总和,如果在一定范围内则可能不可压缩(在 128 * 127 的 10% 以内)(这个速度很快,也比较好,但我正在寻找一些东西更可靠,因为该算法实际上只查看每个字节的最高位)

  • 看文件头(比较靠谱,但感觉像作弊)

我猜大体的想法是,我需要一个能够快速计算出字节列表中每个位的概率是否大约为 0.5 的算法。

更新

我已经实现了“ASCII 检查”、“熵计算”和“简化压缩”,并且都给出了很好的结果。我想改进算法,现在我的想法是不仅要预测数据是否可以压缩,还要预测可以压缩多少。可能使用算法的组合。现在,如果我只能接受多个答案...我将接受给出最佳结果的答案。

仍然欢迎其他答案(新想法)!如果可能的话,提供源代码或链接:-)

更新 2

类似的方法是now implemented in Linux

【问题讨论】:

  • 您可以尝试统计方法(您显然已经考虑过)或根据文件类型事先进行一些估计。我会选择第二个选项并对此进行改进。
  • 嗯,是的,但究竟是哪种统计方法?
  • 我发现如果一个块是可压缩的,查看前 128 个字节就足以得到一个很好的预测。但这只是一个例子。
  • 如果您主要处理完整的文件,而不是使用文件头,甚至可能只处理前 4 个字节,以识别已知的压缩文件格式。 linux file 命令使用一个非常广泛的魔法模式数据库,您可以从中提取所需的信息。
  • @Thomas 不确定前 128 个字节。某些格式在此处具有可压缩的标头。例如。 JAR/zip 文件有文件名列表,主要类似于纯文本,但它有压缩内容。也许值得在整个区块中采样几个小区块。

标签: java compression


【解决方案1】:

计算数据的entropy。如果它具有高熵(~1.0),则不太可能进一步压缩。如果它具有低熵(~0.0),则意味着其中没有很多“信息”,可以进一步压缩。

它提供了一个数据压缩程度的理论度量。

【讨论】:

  • 是的,如何有效地做到这一点?
  • 熵只是一些简单的压缩技术的一个很好的度量,例如。使用普通的霍夫曼编码。常用的压缩格式(gzip、bzip、lzma)使用复杂得多的算法,因此仅凭熵无法确定数据是否可以压缩。
  • @jarnbjo:这是衡量最佳压缩技术所能达到的效果。我不明白这还不够。算法再复杂,也比不上数据的熵。
  • @tskuzzy:请告诉我如何执行这个神奇的通用熵计算。什么是例如字节序列0..255的熵重复1000次(根据你对熵的理解)?
  • jarnbjo 是对的。您似乎暗示计算数据的(实际)熵”是直截了当的,但事实并非如此;您需要做出假设,例如字节是独立的。但是文件的所有字节可能是等概率的,但仍然具有高冗余(低熵)。而且,准确地说,像 gzip 这样的压缩器利用了这种冗余,而这很难测量(作为概率模型)
【解决方案2】:

我实现了一些方法来测试数据是否可压缩。

简化压缩

这基本上检查重复的字节对:

static boolean isCompressible(byte[] data, int len) {
    int result = 0;
    // check in blocks of 256 bytes, 
    // and sum up how compressible each block is
    for (int start = 0; start < len; start += 256) {
        result += matches(data, start, Math.min(start + 255, len));
    }
    // the result is proportional to the number of 
    // bytes that can be saved
    // if we can save many bytes, then it is compressible
    return ((len - result) * 777) < len * 100;
}

static int matches(byte[] data, int i, int end) {
    // bitArray is a bloom filter of seen byte pairs
    // match counts duplicate byte pairs
    // last is the last seen byte
    int bitArray = 0, match = 0, last = 0;
    if (i < 0 || end > data.length) {
        // this check may allow the JVM to avoid
        // array bound checks in the following loop
        throw new ArrayIndexOutOfBoundsException();
    }
    for (; i < end; i++) {
        int x = data[i];
        // the bloom filter bit to set
        int bit = 1 << ((last ^ x) & 31);
        // if it was already set, increment match
        // (without using a branch, as branches are slow)
        match -= (-(bitArray & bit)) >> 31;
        bitArray |= bit;
        last = x;
    }
    return match;
}

在我的(有限的)一组测试数据上,这个算法相当准确。如果数据不可压缩,它比压缩自身快约 5 倍。但是对于琐碎的数据(全为零),它的速度大约是原来的一半。

部分熵

此算法估计高半字节的熵。我想避免使用太多的桶,因为每次都必须将它们清零(如果要检查的块很小,这会很慢)。 63 - numberOfLeadingZeros 是对数(我想避免使用浮点数)。根据数据,它比上面的算法更快或更慢(不知道为什么)。结果不如上面的算法准确,可能是因为只使用了 16 个桶,而且只使用了整数运算。

static boolean isCompressible(byte[] data, int len) {
    // the number of bytes with 
    // high nibble 0, 1,.., 15
    int[] sum = new int[16];
    for (int i = 0; i < len; i++) {
        int x = (data[i] & 255) >> 4;
        sum[x]++;
    }
    // see wikipedia to understand this formula :-)
    int r = 0;
    for (int x : sum) {
        long v = ((long) x << 32) / len;
        r += 63 - Long.numberOfLeadingZeros(v + 1);
    }
    return len * r < 438 * len;
}

【讨论】:

  • 哇,我不怕比特玩弄,但是对于一些 cmets 或命名常量来说,尤其是“2777”部分,我会尖叫。还有,不会last*2777溢出吗?
  • 这只是初始版本...最终版本将包含 cmets。该代码类似于 LZF 和其他压缩算法。 last*2777 应该溢出,因为它是一个散列函数。
  • 感谢您的解释,使用一组字节的哈希函数并计算重复值是个好主意。
  • 我猜你现在有了这些方法的最终版本,你能更新答案吗?
  • 我还没有真正使用过代码(在MVStore),但我尝试过对代码进行注释。
【解决方案3】:

根据我的经验,几乎所有可以有效压缩的格式都是非二进制的。因此,检查大约 70-80% 的字符是否在 [0-127] 范围内应该可以解决问题。

如果你想“正确地”这样做(即使我真的看不出这样做的理由),你要么必须对数据运行(部分)压缩算法,要么计算熵,如 tkuzzy已经提议了。

【讨论】:

  • 这听起来像我到目前为止的想法。计算 128 个字节的总和是检查大多数字符是否在 [0-127] 内的一种简单方法,我已经有了。使用简化的压缩算法(没有输出的算法)也是一个好主意,而且可能比计算熵(这是我目前没有想到的)要好一些。
  • 我已经实现了“ASCII 检查”、“熵计算”和“简化压缩”(见下面我的回答),并且都给出了很好的结果。我想改进算法,现在我的想法是不仅要预测 if 数据可以压缩,还可以预测 多少 可以压缩。可能使用算法的组合。现在,如果我只能接受多个答案...我将接受给出最佳结果的答案。
  • 我将使用部分压缩算法(请参阅下面的答案)。我发现熵计算也不错,但没有那么好,有时会慢一些。
【解决方案4】:

这个问题很有趣,因为例如 zlib 压缩不可压缩数据比压缩可压缩数据需要更长的时间。因此,不成功的压缩特别昂贵(有关详细信息,请参阅链接)。 Harnik 等人最近在这方面做了很好的工作。来自 IBM。

是的,前缀方法和字节序 0 熵(在其他帖子中称为熵)是很好的指标。 猜测文件是否可压缩的其他好方法是(来自论文):

  • 核心集大小 - 构成大部分数据的字符集
  • 符号对分布指示器

这是 FAST paperslides

【讨论】:

    【解决方案5】:

    我希望在您尝试压缩之前无法检查某些内容的可压缩性。 您可以检查模式(更多模式,也许更可压缩),但是特定的压缩算法可能不会使用您检查的模式 - 并且可能比您预期的要好。 另一个技巧可能是获取前 128000 字节的数据,通过 Deflate/Java 压缩将其推送,并查看它是否小于原始大小。如果是这样 - 很有可能压缩整个批次是值得的。

    【讨论】:

      【解决方案6】:

      LZ4 等快速压缩器已经内置了数据可压缩性检查。他们迅速跳过不好的部分,专注于更有趣的部分。 举个恰当的例子,不可压缩数据上的 LZ4 几乎在 RAM 速度限制下工作(我的笔记本电脑上为 2GB/s)。因此,检测器的速度几乎没有空间。 你可以自己试试: http://code.google.com/p/lz4/

      【讨论】:

      • 我不同意,检测器还有很大的空间可以更快。 LZ4 类似于 LZO、LZF 和 Snappy,我已经知道它们的速度有多快。所有这些压缩算法都能检测到不可压缩的块,但它们的检测速度相对较慢。
      • “慢慢地”听起来像是一个苛刻的夸大词。最好的(我不在列表中包括 LZF)已经在不可压缩数据的 RAM 速度限制下工作,并且在提供适当输出的工作时(如果输入不可压缩,则主要是输入的副本)。只需删除输出以提供压缩计数器统计信息,这可能会尽可能快。
      • 正如我所写的,我对 Java 的算法很感兴趣。请注意我写的是“相对较慢”,而不仅仅是“慢”:根据我的测试,最快压缩算法的 Java 版本比我的算法慢得多(不生成输出并且不使用真正的哈希表) )。
      • Lz4 速度很快,但在相同的不可压缩数据上,snappy 通常胜过 lz4。
      【解决方案7】:

      在您的个人资料上显示您是 H2 数据库引擎的作者,这是一个用 Java 编写的数据库。

      如果我猜对了,如果可能的话,您希望设计这个数据库引擎来自动压缩 BLOB 数据。

      但是——(我猜)你已经意识到并不是所有的东西都会被压缩,而且速度很重要——所以在确定是否应该压缩数据时,你不想浪费超过必要的微秒......

      我的问题本质上是工程学——为什么要这样做?基本上,这不是在猜测数据库用户/应用程序开发人员的意图——以牺牲速度为代价吗?

      您是否认为应用程序开发人员(首先将数据写入 blob 字段)将是决定是否压缩数据以及是否压缩数据的最佳人选?合适的压缩方法?

      我可以看到自动数据库压缩可能添加一些值的唯一可能的地方是在 text/varchar 字段中——并且只有当它们超过一定长度时——但即便如此,该选项可能由应用程序更好地决定开发人员...我什至可能会允许应用程序开发人员使用压缩插件,如果是这样...这样他们就可以为自己的数据做出自己的决定...

      如果我对你试图做的事情的假设是错误的——那么我很抱歉我所说的话......(这只是一个微不足道的用户意见。)

      【讨论】:

      • 这个功能实际上是我正在为 Jackrabbit 3 而不是 H2 寻找的功能。这不以牺牲速度为代价(这是计划)。做一些简单的内存计算比将数据存储到磁盘要快。如果数据可以被大量压缩,那么压缩+存储压缩文件可以比只存储未压缩文件更快。
      • 为什么不在后台压缩数据?如果有可用的 CPU 周期,你可以有一个线程来寻找可压缩的对象,尝试压缩它们,如果成功,将它们写回压缩,如果没有,跳过它们并继续......线程检查 CPU 状态并休眠如果 CPU 忙...?
      • 在后台压缩是个好主意,但它要复杂得多,特别是因为数据存储应该是不可变的。用压缩版本替换数据很棘手,因为数据存储可以同时从另一个进程中访问。此外,如果数据需要存储两次,整体吞吐量会更低。
      【解决方案8】:

      还有——为什么不试试 lzop?我个人可以保证它比 bzip、gzip、zip、rar 更快、更快(压缩和解压缩)...

      http://www.lzop.org

      将它用于磁盘映像压缩会使进程受磁盘 IO 限制。使用任何其他压缩器都会使进程受 CPU 限制(即,其他压缩器使用所有可用的 CPU,lzop(在合理的 CPU 上)可以以与 7200 RPM 库存硬盘驱动器相同的速度处理数据...... )

      我敢打赌,如果您使用“测试压缩”字符串的前 X 个字节对其进行测试,它会比大多数其他方法快得多...

      【讨论】:

      • 你知道LZF和Snappy吗?它们与 LZO 属于同一类别。你能给我指点 LZO compression 算法的开源纯 Java 实现吗(可用的不是开源的)?
      • 我找到了 LZO 的一个版本,但它是用 C 编写的,并使用特殊的预处理器/转换器转换为 Java。我还发现一些基准测试结果表明 LZF(纯 Java,源代码可用)和 Snappy(本机!)与 LZO 一样快。
      猜你喜欢
      • 1970-01-01
      • 2016-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-25
      • 1970-01-01
      相关资源
      最近更新 更多