Java不同压缩算法的性能比较

本文将会对常用的几个压缩算法的性能作一下比较。结果表明,某些算法在极端苛刻的CPU限制下仍能正常工作。

文中进行比较的算有:

  • JDK GZIP ——这是一个压缩比高的慢速算法,压缩后的数据适合长期使用。JDK中的java.util.zip.GZIPInputStream / GZIPOutputStream便是这个算法的实现。
  • JDK deflate ——这是JDK中的又一个算法(zip文件用的就是这一算法)。它与gzip的不同之处在于,你可以指定算法的压缩级别,这样你可以在压缩时间和输出文件大小上进行平衡。可选的级别有0(不压缩),以及1(快速压缩)到9(慢速压缩)。它的实现是java.util.zip.DeflaterOutputStream / InflaterInputStream。
  • LZ4压缩算法Java实现——这是本文介绍的算法中压缩速度最快的一个,与最快速的deflate相比,它的压缩的结果要略微差一点。如果想搞清楚它的工作原理,我建议你读一下这篇文章。它是基于友好的Apache 2.0许可证发布的。
  • Snappy——这是Google开发的一个非常流行的压缩算法,它旨在提供速度与压缩比都相对较优的压缩算法。我用来测试的是这个实现。它也是遵循Apache 2.0许可证发布的。

压缩测试

要找出哪些既适合进行数据压缩测试又存在于大多数Java开发人员的电脑中(我可不希望你为了运行这个测试还得个几百兆的文件)的文件也着实费了我不少工夫。最后我想到,大多数人应该都会在本地安装有JDK的文档。因此我决定将javadoc的目录整个合并成一个文件——拼接所有文件。这个通过tar命令可以很容易完成,但并非所有人都是Linux用户,因此我写了个程序来生成这个文件:

public class InputGenerator {
    private static final String JAVADOC_PATH = "your_path_to_JDK/docs";
    public static final File FILE_PATH = new File( "your_output_file_path" );

    static
    {
        try {
            if ( !FILE_PATH.exists() )
                makeJavadocFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void makeJavadocFile() throws IOException {
        try( OutputStream os = new BufferedOutputStream( new FileOutputStream( FILE_PATH ), 65536 ) )
        {
            appendDir(os, new File( JAVADOC_PATH ));
        }
        System.out.println( "Javadoc file created" );
    }

    private static void appendDir( final OutputStream os, final File root ) throws IOException {
        for ( File f : root.listFiles() )
        {
            if ( f.isDirectory() )
                appendDir( os, f );
            else
                Files.copy(f.toPath(), os);
        }
    }
}

在我的机器上整个文件的大小是354,509,602字节(338MB)。

测试

一开始我想把整个文件读进内存里,然后再进行压缩。不过结果表明这么做的话即便是4G的机器上也很容易把堆内存空间耗尽。

于是我决定使用操作系统的文件缓存。这里我们用的测试框架是JMH。这个文件在预热阶段会被操作系统加载到缓存中(在预热阶段会先压缩两次)。我会将内容压缩到ByteArrayOutputStream流中(我知道这并不是最快的方法,但是对于各个测试而言它的性能是比较稳定的,并且不需要花费时间将压缩后的数据写入到磁盘里),因此还需要一些内存空间来存储这个输出结果。

 

下面是测试类的基类。所有的测试不同的地方都只在于压缩的输出流的实现不同,因此可以复用这个测试基类,只需从StreamFactory实现中生成一个流就好了:

@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 2)
@Measurement(iterations = 3)
@BenchmarkMode(Mode.SingleShotTime)
public class TestParent {
    protected Path m_inputFile;

    @Setup
    public void setup()
    {
        m_inputFile = InputGenerator.FILE_PATH.toPath();
    }

    interface StreamFactory
    {
        public OutputStream getStream( final OutputStream underlyingStream ) throws IOException;
    }

    public int baseBenchmark( final StreamFactory factory ) throws IOException
    {
        try ( ByteArrayOutputStream bos = new ByteArrayOutputStream((int) m_inputFile.toFile().length());
              OutputStream os = factory.getStream( bos ) )
        {
            Files.copy(m_inputFile, os);
            os.flush();
            return bos.size();
        }
    }
}

 

这些测试用例都非常相似(在文末有它们的源代码),这里只列出了其中的一个例子——JDK deflate的测试类;

 

public class JdkDeflateTest extends TestParent {
    @Param({"1", "2", "3", "4", "5", "6", "7", "8", "9"})
    public int m_lvl;

    @Benchmark
    public int deflate() throws IOException
    {
        return baseBenchmark(new StreamFactory() {
            @Override
            public OutputStream getStream(OutputStream underlyingStream) throws IOException {
                final Deflater deflater = new Deflater( m_lvl, true );
                return new DeflaterOutputStream( underlyingStream, deflater, 512 );
            }
        });
    }
}

测试结果

输出文件的大小

首先我们来看下输出文件的大小:

实现文件大小(字节)
GZIP64,200,201
Snappy (normal)138,250,196
Snappy (framed)101,470,113
LZ4 (fast)98,316,501
LZ4 (high)82,076,909
Deflate (lvl=1)78,369,711
Deflate (lvl=2)75,261,711
Deflate (lvl=3)73,240,781
Deflate (lvl=4)68,090,059
Deflate (lvl=5)65,699,810
Deflate (lvl=6)64,200,191
Deflate (lvl=7)64,013,638
Deflate (lvl=8)63,845,758
Deflate (lvl=9)63,839,200

https://deepinmind.oss-cn-beijing.aliyuncs.com/file_size2.png

可以看出文件的大小相差悬殊(从60Mb到131Mb)。我们再来看下不同的压缩方法需要的时间是多少。

压缩时间

实现压缩时间(ms)
Snappy.framedOutput2264.700
Snappy.normalOutput2201.120
Lz4.testFastNative1056.326
Lz4.testFastUnsafe1346.835
Lz4.testFastSafe1917.929
Lz4.testHighNative7489.958
Lz4.testHighUnsafe10306.973
Lz4.testHighSafe14413.622
deflate (lvl=1)4522.644
deflate (lvl=2)4726.477
deflate (lvl=3)5081.934
deflate (lvl=4)6739.450
deflate (lvl=5)7896.572
deflate (lvl=6)9783.701
deflate (lvl=7)10731.761
deflate (lvl=8)14760.361
deflate (lvl=9)14878.364
GZIP10351.887

image

我们再将压缩时间和文件大小合并到一个表中来统计下算法的吞吐量,看看能得出什么结论。

吞吐量及效率

实现时间(ms)未压缩文件大小吞吐量(Mb/秒)压缩后文件大小(Mb)
Snappy.normalOutput2201.12338153.5581885586131.8454742432
Snappy.framedOutput2264.7338149.247140901796.7693328857
Lz4.testFastNative1056.326338319.976976804593.7557220459
Lz4.testFastSafe1917.929338176.231758318593.7557220459
Lz4.testFastUnsafe1346.835338250.958729168893.7557220459
Lz4.testHighNative7489.95833845.127088830178.2680511475
Lz4.testHighSafe14413.62233823.450039136678.2680511475
Lz4.testHighUnsafe10306.97333832.793333212478.2680511475
deflate (lvl=1)4522.64433874.735044367974.7394561768
deflate (lvl=2)4726.47733871.512037401271.7735290527
deflate (lvl=3)5081.93433866.510112095169.8471069336
deflate (lvl=4)6739.4533850.152460512464.9452209473
deflate (lvl=5)7896.57233842.803383544262.6564025879
deflate (lvl=6)9783.70133834.547253641561.2258911133
deflate (lvl=7)10731.76133831.495296997461.0446929932
deflate (lvl=8)14760.36133822.899168929560.8825683594
deflate (lvl=9)14878.36433822.717551472760.8730316162
GZIP10351.88733832.65105192961.2258911133

image

可以看到,其中大多数实现的效率是非常低的:在Xeon E5-2650处理器上,高级别的deflate大约是23Mb/秒,即使是GZIP也就只有33Mb/秒,这大概很难令人满意。同时,最快的defalte算法大概能到75Mb/秒,Snappy是150Mb/秒,而LZ4(快速,JNI实现)能达到难以置信的320Mb/秒!

从表中可以清晰地看出目前有两种实现比较处于劣势:Snappy要慢于LZ4(快速压缩),并且压缩后的文件要更大。相反,LZ4(高压缩比)要慢于级别1到4的deflate,而输出文件的大小即便和级别1的deflate相比也要大上不少。

因此如果需要进行“实时压缩”的话我肯定会在LZ4(快速)的JNI实现或者是级别1的deflate中进行选择。当然如果你的公司不允许使用第三方库的话你也只能使用deflate了。你还要综合考虑有多少空闲的CPU资源以及压缩后的数据要存储到哪里。比方说,如果你要将压缩后的数据存储到HDD的话,那么上述100Mb/秒的性能对你而言是毫无帮助的(假设你的文件足够大的话)——HDD的速度会成为瓶颈。同样的文件如果输出到SSD硬盘的话——即便是LZ4在它面前也显得太慢了。如果你是要先压缩数据再发送到网络上的话,最好选择LZ4,因为deflate75Mb/秒的压缩性能跟网络125Mb/秒的吞吐量相比真是小巫见大巫了(当然,我知道网络流量还有包头,不过即使算上了它这个差距也是相当可观的)。

总结

  • 如果你认为数据压缩非常慢的话,可以考虑下LZ4(快速)实现,它进行文本压缩能达到大约320Mb/秒的速度——这样的压缩速度对大多数应用而言应该都感知不到。
  • 如果你受限于无法使用第三方库或者只希望有一个稍微好一点的压缩方案的话,可以考虑下使用JDK deflate(lvl=1)进行编解码——同样的文件它的压缩速度能达到75Mb/秒。

相关代码:

链接:https://pan.baidu.com/s/1avz1Db_YlmQw1AHdY-4n_A 
提取码:ys6v

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值