Artikel ini akan membandingkan kinerja beberapa algoritma kompresi yang umum digunakan. Hasilnya menunjukkan bahwa beberapa algoritma masih berfungsi dengan baik di bawah kendala CPU yang sangat menuntut.
Perbandingan dalam artikel ini meliputi:
JDK GZIP - Ini adalah algoritma yang lambat dengan rasio kompresi tinggi, dan data terkompresi cocok untuk penggunaan jangka panjang. java.util.zip.gzipinputStream / gzipoutputStream di JDK adalah implementasi algoritma ini.
JDK Deflate - Ini adalah algoritma lain di JDK (algoritma ini digunakan dalam file zip). Yang membuatnya berbeda dari GZIP adalah Anda dapat menentukan tingkat kompresi algoritma sehingga Anda dapat menyeimbangkan waktu kompresi dan ukuran file output. Level opsional adalah 0 (tidak terkompresi), dan 1 (terkompresi cepat) hingga 9 (kompresi lambat). Implementasinya adalah java.util.zip.deflateroutputStream/inflaterinputStream.
Implementasi Java dari algoritma kompresi LZ4 - ini adalah kecepatan kompresi tercepat di antara algoritma yang diperkenalkan dalam artikel ini. Dibandingkan dengan kempa tercepat, hasil kompresinya sedikit lebih buruk.
Snappy - Ini adalah algoritma kompresi yang sangat populer yang dikembangkan oleh Google. Ini bertujuan untuk memberikan algoritma kompresi dengan kecepatan dan rasio kompresi yang relatif baik.
Tes kompresi
Saya juga butuh banyak waktu untuk mengetahui file mana yang cocok untuk pengujian kompresi data dan ada di sebagian besar komputer pengembang Java (saya tidak ingin Anda memiliki beberapa ratus megabyte file untuk menjalankan tes ini). Akhirnya, saya berpikir bahwa kebanyakan orang harus memiliki dokumentasi untuk JDK yang diinstal secara lokal. Oleh karena itu, saya memutuskan untuk menggabungkan seluruh direktori Javadoc menjadi satu file - menyambungkan semua file. Ini dapat dilakukan dengan mudah dengan perintah tar, tetapi tidak semua orang adalah pengguna Linux, jadi saya menulis program untuk menghasilkan file ini:
InputGenerator kelas publik {private static final string javadoc_path = "your_path_to_jdk/docs"; file final statis public file_path = file baru ("your_output_file_path"); static {coba {if (! File_path.exists ()) makeJavAdocFile (); } catch (ioException e) {e.printstacktrace (); }} private static void makeJavAdocFile () melempar ioException {try (outputStream os = baru bufferedOutputStream (fileoutputStream baru (file_path), 65536)) {appendDir (OS, file baru (javadoc_path))); } System.out.println ("File Javadoc dibuat"); } private static void appendDir (outputStream os akhir, root file akhir) melempar ioException {for (file f: root.listFiles ()) {if (f.isdirectory ()) appendDir (os, f); else file.copy (f.topath (), os); }}}Ukuran seluruh file pada mesin saya adalah 354.509.602 byte (338MB).
tes
Pada awalnya saya ingin membaca seluruh file ke dalam memori dan kemudian mengompresnya. Namun, hasilnya menunjukkan bahwa bahkan mesin 4G dapat dengan mudah kehabisan ruang memori.
Jadi saya memutuskan untuk menggunakan cache file dari sistem operasi. Kerangka tes yang kami gunakan di sini adalah JMH. File ini akan dimuat ke dalam cache oleh sistem operasi selama fase pemanasan (akan dikompresi dua kali dalam fase pemanasan). Saya akan mengompres konten ke dalam stream ByteArrayOutputStream (saya tahu ini bukan cara tercepat, tetapi relatif stabil untuk setiap tes dan tidak membutuhkan waktu untuk menulis data terkompresi ke disk), sehingga beberapa ruang memori diperlukan untuk menyimpan output ini.
Di bawah ini adalah kelas dasar dari kelas uji. Semua tes hanya berbeda dalam berbagai implementasi aliran output terkompresi, sehingga Anda dapat menggunakan kembali kelas dasar tes ini dan hanya menghasilkan aliran dari implementasi streamFactory:
@OutputTimeUnit (TimeUnit.Milliseconds) @State (scope.thread) @fork (1) @warmup (iterasi = 2) @MeAingurement (iterasi = 3) @benchmarkMode (mode.singleshotTime) kelas publik testparent {dilindungi m_inputFile; @Setup public void setup () {m_inputFile = inputGenerator.file_path.topath (); } antarmuka streamFactory {outputStream publik getStream (final outputStream underlyingstream) melempar ioException; } public int basebenchmark (Final StreamFactory Factory) melempar ioException {try (bytearrayoutputStream bos = bytearrayoutputStream ((int) m_inputFile.tofile (). length ()); outputStream os = factory.getStream (bos) {file. os.flush (); return bos.size (); }}}Kasus -kasus uji ini sangat mirip (kode sumbernya tersedia di akhir artikel), dan hanya satu contoh yang tercantum di sini - kelas pengujian dari JDK mengempis;
Kelas Publik JDKDEFLATETEST memperluas 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 ); } }); }}Hasil tes
Ukuran file output
Pertama, mari kita lihat ukuran file output:
|| Implementasi || ukuran file (bytes) |||| gzip || 64.200.201 |||| snappy (normal) || 138.250.196 |||| snappy (framed) || 101.470.113 |||||| lz4 (cepat) || 98.316.501 |||| lz4 (tinggi) || 82.076.909 ||| Disemplasi (lvl = 1) || 78.369.711,||| (lvl = 4) || 68.090.059 || || Deflate (lvl = 5) || 65.699.810 |||| Disemplasi (lvl = 6) || 64.200.191 ||||| Disemplasi (lvl = 7) || 64.013,638 ||| Disemplasi (lvl = 8) || 64.013,638 ||| || 63.839.200 |||
Dapat dilihat bahwa ukuran file sangat bervariasi (dari 60MB hingga 131MB). Mari kita lihat berapa lama waktu yang dibutuhkan untuk metode kompresi yang berbeda.
Waktu kompresi
|| Implementasi || Kompresi waktu (ms) |||| snappy.framedOutput || 2264.700 |||| snappy.normaloutput || 2201.120 |||| lz4.testfastnative || 1056.326 |||| lz4.testfastunsa Fe || 1346.835 |||| lz4.testfastsafe || 1917.929 |||| lz4.testhighnative || 7489.958 |||| lz4.testhighunsafe || 10306.973 ||||||||||||||||| || 14413.622 |||| Disemplasi (lvl = 1) || 4522.644 |||| Disemplasi (lvl = 2) || 4726.477 |||| Deflate (lvl = 3) || 5081.934 ||| || 7896.572 |||| Deflate (lvl = 6) || 9783.701 |||| Disemplasi (lvl = 7) || 10731.761 |||| Disemplasi (lvl = 8) || 14760.361 ||| || 10351.887 ||
Kami kemudian menggabungkan waktu kompresi dan ukuran file ke dalam tabel untuk menghitung throughput algoritma dan melihat kesimpulan apa yang dapat ditarik.
Throughput dan efisiensi
|| Implementasi || Waktu (MS) || Ukuran File Tidak Terkompresi || Throughput (MB/Sec) ||| ukuran file terkompresi (MB) |||| snappy.normaloutput || 2201.12 || 338 || | | | | | | | | | | | ||| || || iPy || || 149.2471409017 || 96.7693328857 ||||| lz4.testfastnative || 1056.326 || 338 || 319.9769768045 || 93.7557220459 ||||| iBz4.| || 176.2317583185 || 93.757220459 ||||||| lz4.testfastunsafe || 1346.835 || 338 || 250.9587291688 || 93.757220459 ||| ||| || || || 45.1270888301 || 78.2680511475 ||||| lz4.testhighsafe || 14413.622 || 338 || 23.4500391366 || 78.2680511475 ||||||||||| iFRIGHIGHIGHIFHIGHTHIGHIFHIGHIFHIGHIGHIFHIGHIGHIFHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIGHIF. || 32.7933332124 || 78.2680511475 ||||| || 71.5120374012 || 71.7735290527 |||| DISFLATE (lvl = 3) || 5081.934 || 338 || 66.5101120951 || 69.8471069336 ||| iFlate (lvl = 4) || || 50.1524605124 || 64.9452209473 ||||| || 34.5472536415 || 61.2258911133 |||||| || 22.8991689295 || 60.8825683594 |||| Disemplate (lvl = 9) || 14878.364 || 338 || 22.7175514727 || 60.8730316162 || i 22.717514727.| || 61.2258911133 ||
Seperti yang dapat dilihat, sebagian besar implementasi sangat tidak efisien: pada prosesor Xeon E5-2650, kempa tingkat tinggi adalah sekitar 23MB/detik, dan bahkan GZIP hanya 33MB/detik, yang mungkin sulit dipenuhi. Pada saat yang sama, algoritma Defalte tercepat dapat mencapai sekitar 75MB/detik, Snappy adalah 150MB/detik, dan LZ4 (FAST, Implementasi JNI) dapat mencapai 320MB/detik yang luar biasa!
Dapat dilihat dengan jelas dari tabel bahwa saat ini ada dua implementasi yang kurang menguntungkan: Snappy lebih lambat dari LZ4 (kompresi cepat), dan file terkompresi lebih besar. Sebaliknya, LZ4 (rasio kompresi tinggi) lebih lambat daripada mengempis dari level 1 hingga 4, dan ukuran file output jauh lebih besar daripada kempes dari level 1.
Oleh karena itu, jika Anda perlu melakukan "kompresi real-time", saya pasti akan memilih dalam implementasi JNI LZ4 (cepat) atau level 1 mengempis. Tentu saja, jika perusahaan Anda tidak mengizinkan perpustakaan pihak ketiga, Anda hanya dapat menggunakan Deflate. Anda juga perlu mempertimbangkan secara komprehensif berapa banyak sumber daya CPU gratis yang ada dan di mana data terkompresi disimpan. Misalnya, jika Anda ingin menyimpan data terkompresi ke HDD, kinerja 100MB/s di atas tidak bermanfaat bagi Anda (dengan asumsi file Anda cukup besar) - kecepatan HDD akan menjadi hambatan. Jika file yang sama output ke hard drive SSD - bahkan LZ4 akan tampak terlalu lambat di depannya. Jika Anda ingin mengompres data terlebih dahulu dan kemudian mengirimkannya ke jaringan, yang terbaik adalah memilih LZ4, karena kinerja kompresi Deflate75Mb/s sangat kecil dibandingkan dengan throughput 125MB/s (tentu saja, saya tahu bahwa ada juga Baotou dalam lalu lintas jaringan, tetapi bahkan jika dimasukkan, celahnya cukup wajib).
Meringkaskan
Jika Anda berpikir kompresi data sangat lambat, Anda dapat mempertimbangkan implementasi LZ4 (cepat), yang dapat mencapai kecepatan kompresi teks sekitar 320MB/detik - kecepatan kompresi seperti itu tidak boleh dirasakan untuk sebagian besar aplikasi.
Jika Anda terbatas karena tidak dapat menggunakan pustaka pihak ketiga atau hanya ingin memiliki solusi kompresi yang sedikit lebih baik, Anda dapat mempertimbangkan untuk menggunakan JDK Deflate (LVL = 1) untuk pengkodean dan decoding - file yang sama dapat mengompres file yang sama ke 75MB/s.
Kode Sumber
Kode Sumber Tes Kompresi Java