如何减少GZIPOutputStream的时间

3

我尝试对一个大的XML文件(100MB到500MB)进行gzip压缩。我创建了一个名为Zip的方法来完成这个任务。问题是,它花费的时间太长了。对于200MB的文件,需要1.2秒的时间。我需要将时间减少到100毫秒,以处理100MB的XML文件。

我通过在一定程度上牺牲压缩比例来缩短时间。我也尝试了其他算法,如Snappy、Lz4,但提升不大,并且它们的压缩效果较差。据我所知,gzipOutputStream.write() 占用了85%的时间。那么,我该如何优化这一步骤,以获得更好的性能而不会过多牺牲压缩比例呢?

public static String zip(final String str) {
    if ((str == null) || (str.length() == 0)) {
        throw new IllegalArgumentException("Cannot zip null or empty string");
    }

    try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(str.length())) {
        try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(byteArrayOutputStream){{def.setLevel(Deflater.BEST_SPEED );}};) {
            gzipOutputStream.write(str.getBytes(StandardCharsets.UTF_8));

        } 
            T5 = System.currentTimeMillis();
            byte[] bytes=byteArrayOutputStream.toByteArray();
             T3 = System.currentTimeMillis();

            String zipped_text=DatatypeConverter.printBase64Binary(bytes);
             T4 = System.currentTimeMillis();
            return zipped_text;

    } catch(IOException e) {
        throw new RuntimeException("Failed to zip content", e);
    }

}

1
去除压缩,测量时间 - 这可能是您的时间渐近线。 - Antoniossss
你基本上是在问如何让一段没有任何速度优化的代码变快12倍。答案是:使用一个实现速度优化的压缩算法。然后你的硬件可能仍然是瓶颈。 - Gimby
在每个步骤中,您始终在处理整个块。100MB 太大了,无法适应任何 CPU 缓存。因此,请使用约 500KB 的块处理数据,并直接使用流重定向输出,例如使用 Apache Commons Codec 中的 Base64OutputStream。 - Robert
@Robert,我是Java的新手。你能提供优化后的代码或示例吗?感谢你的时间。 - SHUHAIB AREEKKAN
不要试图在内存中完成此操作。边进行操作时,将其写入目标文件、套接字或任何其他位置。 - user207421
1个回答

1
这是我的建议:
  1. 创建一个适当的基准,以便您可以获得可重复的结果。我建议使用基准测试框架,例如JMH。

  2. 对您的代码/基准进行分析,以确定瓶颈/热点位置,例如使用jVisualVM或Java Mission Control Flight Recorder。

  3. 使用基准测试和分析结果指导您的优化工作。

(出于各种原因,我不会仅依赖调用System.currentTimeMillis()。)

可能的一个解释是,在以下步骤中,花费了大量时间来复制数据。

  • 创建包含XML的输入字符串
  • ByteArrayOutputStream中捕获压缩字节
  • 将字节转换为另一个字符串。

因此,如果您正在寻找改进的方法,请尝试安排事情,使XML序列化器写入流经过gzip和base64转换的管道,然后直接写入文件或套接字流。

此外,如果可能的话,建议避免使用base64。如果压缩后的XML在HTTP响应中,可以以二进制形式发送。这样会更快,生成的网络流量也会显著减少。最后,选择一个在压缩比和压缩时间之间取得良好平衡的压缩算法。
如果您正在尝试这样做,那么您的目标可能是错误的。(为什么您要对压缩文件进行Base64编码呢?这与您的目标相矛盾!)

更新以解决您的评论:

  1. 我认为,将XML转换为字符串,然后调用getBytes()方法比流式传输更能提高性能。首先,getBytes()调用会使字符串内容多做一次不必要的复制。

  2. 无损压缩的维基百科页面链接了许多算法,其中许多应该有Java实现可供使用。此外,它还链接了许多基准测试。我没有看过基准测试链接,但我期望至少有一个可以量化不同算法之间的压缩与计算时间的权衡。

  3. 如果将数据库表从CLOB更改为BLOB:

    • 您可以省去base64,节省约25%的存储空间
    • 您可以省去base64编码步骤,节省几个百分点的CPU
    • 然后您可以选择一个更快(但不太紧凑)的算法,以节省更多时间,代价是一些通过转换为BLOB而节省的空间。
  4. "我真的不能改变它,这是业务需求。" - 真的吗?如果数据库模式是业务需求,那么你的业务就有很大问题。另一方面,如果业务在那个层面上决定技术,那么他们也在决定性能。

    没有任何合理的技术原因将压缩数据存储为CLOB。

  5. 正如某人所指出的,获得更快的压缩最简单的方法是购买一台更快的计算机。或者(我的想法)购买一组计算机,以便可以并行压缩多个文件。


感谢您的回答,base64用于将字符串存储为CLOB在数据库中。我无法真正改变它,因为这是业务需求。我可以在压缩比率上妥协10%以获得更好的性能,因为我是Java的新手。请提供与您的答案相关的示例或链接,这对我很有帮助。感谢您的时间。 - SHUHAIB AREEKKAN
gzipOutputStream.write(str.getBytes(StandardCharsets.UTF_8)); 这一步占程序85%的时间,我该如何优化它。 - SHUHAIB AREEKKAN
@SHUHAIB 为了加快这一步骤,请购买一台性能更高的电脑(具有高单线程性能)。压缩是计算密集型的,因此需要一些时间。 - Robert
另外,请注意,当基准涉及I/O时,1)操作系统级别存在预热效应;例如Linux上的缓冲区缓存行为,以及2)由于文件系统/网络变异性,您需要运行几次重复。我们真正想要优化的是压缩数据并将其写入某个位置的实际时间。这不是“最少CPU == 最佳”的情况。 - Stephen C
当然:制作基准测试很容易。制作一个能够说出真相™的基准测试则非常困难,甚至接近不可能。我不确定如何处理JMH中的OS/HDD缓存(根据您的第二条评论:)。如果目标是处理单个文件一次,而实际时间花费在读/写文件上(一次!),那么优化10个JMH运行中最慢的方法是没有意义的。重复运行JMH甚至会扭曲结果吗?(这只是直觉 - 我对JMH并不是非常熟悉。其中一个主要原因是即使是最小的基准测试在JMH中运行也需要很长时间...) - Marco13
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接