Este artículo comparará el rendimiento de varios algoritmos de compresión de uso común. Los resultados muestran que algunos algoritmos todavía funcionan correctamente bajo restricciones de CPU extremadamente exigentes.
Las comparaciones en el artículo incluyen:
JDK GZIP: este es un algoritmo lento con alta relación de compresión, y los datos comprimidos son adecuados para su uso a largo plazo. java.util.zip.gzipinputstream / gzipoutputstream en jdk es la implementación de este algoritmo.
JDK Deflate: este es otro algoritmo en JDK (este algoritmo se usa en archivos zip). Lo que lo hace diferente de GZIP es que puede especificar el nivel de compresión del algoritmo para que pueda equilibrar el tiempo de compresión y el tamaño del archivo de salida. Los niveles opcionales son 0 (no comprimidos) y 1 (comprimido rápido) a 9 (comprimido lento). Su implementación es java.util.zip.deflateroutputstream/inflaterInputStream.
La implementación de Java del algoritmo de compresión LZ4: esta es la velocidad de compresión más rápida entre los algoritmos introducidos en este artículo. En comparación con la deflación más rápida, sus resultados de compresión son ligeramente peores.
Snappy: este es un algoritmo de compresión muy popular desarrollado por Google. Su objetivo es proporcionar algoritmos de compresión con una relación de velocidad y compresión relativamente buena.
Prueba de compresión
También me llevó mucho tiempo averiguar qué archivos son adecuados para las pruebas de compresión de datos y existen en la mayoría de las computadoras de Java Developers (no quiero que tenga unos pocos cientos de megabytes de archivos para ejecutar esta prueba). Finalmente, pensé que la mayoría de las personas deberían tener la documentación para JDK instalada localmente. Por lo tanto, decidí fusionar todo el directorio Javadoc en un archivo, empalmando todos los archivos. Esto se puede hacer fácilmente con el comando TAR, pero no todos son un usuario de Linux, por lo que escribí un programa para generar este archivo:
public class InputGenerator {private static final String javadoc_path = "your_path_to_jdk/docs"; Public static final file_path = nuevo archivo ("Your_output_file_path"); static {try {if (! file_path.exists ()) makeJavadoCfile (); } catch (ioException e) {E.PrintStackTrace (); }} private static void makeJavadoCfile () lanza IOException {try (outputStream OS = new BufferedOutputStream (new FileOutputStream (file_path), 65536)) {appendDir (OS, nuevo archivo (javadoc_path)); } System.out.println ("Archivo Javadoc creado"); } private static void appendDir (OS de salida final OS, Final File Root) lanza IOException {for (archivo f: root.listfiles ()) {if (f.isDirectory ()) appendDir (OS, f); else fils.copy (F.Topath (), OS); }}}El tamaño de todo el archivo en mi máquina es de 354,509,602 bytes (338MB).
prueba
Al principio quería leer todo el archivo en la memoria y luego comprimirlo. Sin embargo, los resultados muestran que incluso las máquinas 4G pueden quedarse sin espacio de memoria de Heap.
Así que decidí usar el caché de archivos del sistema operativo. El marco de prueba que usamos aquí es JMH. El sistema operativo cargará este archivo en caché durante la fase de calentamiento (se comprimirá dos veces en la fase de calentamiento). Comprimiré el contenido en la transmisión ByteArArAyOutPutStream (sé que esta no es la forma más rápida, pero es relativamente estable para cada prueba y no lleva tiempo escribir los datos comprimidos en el disco), por lo que se necesita algo de espacio de memoria para almacenar esta salida.
A continuación se muestra la clase base de la clase de prueba. Todas las pruebas son diferentes solo en las diferentes implementaciones de la secuencia de salida comprimida, por lo que puede reutilizar esta clase de base de prueba y simplemente generar una secuencia a partir de la implementación de flujo de flujo:
@OutputtimeUnit (TimeUnit.MilliseConds) @state (Scope.thread) @fork (1) @Warmup (iterations = 2) @Measurement (iterations = 3) @benchmarkmode (mode.singleshottime) Public Class TestParent {ruta protegida M_InputFile; @Setup public void setup () {m_inputFile = inputGenerator.file_path.topath (); } Interface StreamFactory {public OutputStream GetStream (final de salida final UnderlyingStream) lanza IOException; } public int BaseBenchmark (Final StreamFactory Factory) lanza IOException {try (byteArAyOutputStream bos = new ByTearRayOutPutStReam ((int) m_inputfile.tofile (). Longitud ()); OutputStream OS = factory.getStream (BOS)) {archivos.copy (m_inputfile, OS); OS.Flush (); return bos.size (); }}}Estos casos de prueba son muy similares (su código fuente está disponible al final del artículo), y solo se enumera un ejemplo aquí: la clase de prueba de JDK Deflate;
clase pública jdkdeflateTest extiende testParent {@param ({"1", "2", "3", "4", "5", "6", "7", "8", "9"}) public int m_lvl; @Benchmark public int Deflate () lanza IOException {return BaseBenchmark (new StreamFactory () {@Override public OutputStream GetStream (OutputStream UnderlyingStream) lanza IOException {final DeFlater DeFlater = new DeFlater (m_lvl, true); return NewFlateroutStream (UnderlyStream, Deflater, 512);};};};};};};};};}; }}Resultados de las pruebas
El tamaño del archivo de salida
Primero, veamos el tamaño del archivo de salida:
|| Implementación || Tamaño del archivo (bytes) |||| gzip || 64,200,201 |||| Snappy (normal) || 138,250,196 |||| Snappy (enmarcado) || 101,470,113 ||||| lz4 (rápido) || 98,316,501 |||| lz4 (alto) || 82,076,909 ||| Deflate (lvl = 1) || 78,369,711 |||| Deflate (lvl = 2) || 75,261,711 |||| AFLATE (LVL = 3) || 73,240,781 ||| (lvl = 4) || 68,090,059 || || desinflar (lvl = 5) || 65,699,810 |||| desinflate (lvl = 6) || 64,200,191 |||||| Deflate (lvl = 7) || 64,013,638 |||| deflate (lvl = 8) || 63,845,758 |||| || 63,839,200 |||
Se puede ver que el tamaño del archivo varía mucho (de 60 MB a 131 MB). Echemos un vistazo a cuánto tiempo lleva los diferentes métodos de compresión.
Tiempo de compresión
|| Implementación || Compresión Tiempo (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 |||| lz4.testhighsafe || 14413.622 |||| Deflate (lvl = 1) || 4522.644 |||| Deflate (lvl = 2) || 4726.477 |||| - Deflate (LVL = 3) || 5081.934 |||| || 7896.572 |||| Deflate (lvl = 6) || 9783.701 |||| Aflate (lvl = 7) || 10731.761 ||||| Deflate (lvl = 8) || 14760.361 |||| || 10351.887 ||
Luego fusionamos el tiempo de compresión y el tamaño del archivo en una tabla para contar el rendimiento del algoritmo y ver qué conclusiones se pueden sacar.
Rendimiento y eficiencia
|| Implementación || Tiempo (MS) || Tamaño de archivo sin comprimir || Rendimiento (MB/SEC) ||| Tamaño de archivo comprimido (MB) |||| || 149.2471409017 || 96.7693328857 |||| lz4.testfastnative || 1056.326 || 338 || 319.9769768045 || 93.75557220459 |||||| lz4.testfastsafe || 1917.929 || 338 || 176.2317583185 || 93.7557220459 |||||| lz4.testfastunsafe || 1346.835 || 338 || 250.9587291688 || 93.75572220459 ||||| lz4.testhighnative || 7489.958 || || 45.1270888301 || 78.2680511475 ||||| lz4.testhighsafe || 14413.622 || 338 || 23.4500391366 || 78.2680511475 ||||| lz4.testhighunsafe || 10306.973 || || 32.79333332124 || 78.2680511475 |||| Join (lvl = 1) || 4522.644 || 338 || 74.7350443679 || 74.73394561768 |||| (LVL = 2) || 4726.477 || || 71.5120374012 || 71.7735290527 |||| - Deflate (lvl = 3) || 5081.934 || 338 || 66.5101120951 || 69.84471069336 |||| || 50.1524605124 || 64.9452209473 ||||| Deflate (lvl = 5) || 7896.572 || 338 || 42.80338354442 || 62.6564025879 |||| (LVL = 6) || 9783.701 || || 34.5472536415 || 61.2258911133 |||||| Deflate (lvl = 7) || 10731.761 || 338 || 31.49529699974 || 61.0446992932 ||| || 22.8991689295 || 60.8825683594 |||| - Deflate (lvl = 9) || 14878.364 || 338 || 22.7175514727 || 60.8730316162 |||| gzip || 10351.887 || 338 || || 61.2258911133 ||
Como se puede ver, la mayoría de las implementaciones son muy ineficientes: en los procesadores Xeon E5-2650, la deflación de alto nivel es de aproximadamente 23 MB/seg, e incluso GZIP es de solo 33 MB/seg, lo que probablemente sea difícil de satisfacer. Al mismo tiempo, el algoritmo Defalte más rápido puede alcanzar aproximadamente 75 MB/seg, Snappy es 150 MB/seg, y LZ4 (implementación rápida, JNI) puede alcanzar un increíble 320 MB/seg!
Se puede ver claramente desde la tabla que actualmente hay dos implementaciones que están en desventaja: Snappy es más lento que LZ4 (compresión rápida), y el archivo comprimido es más grande. Por el contrario, LZ4 (alta relación de compresión) es más lenta que la desinfla de los niveles 1 a 4, y el tamaño del archivo de salida es mucho mayor que el de la desinflamiento del nivel 1.
Por lo tanto, si necesita realizar una "compresión en tiempo real", definitivamente elegiré en la implementación de JNI LZ4 (rápida) o la desinfla de nivel 1. Por supuesto, si su empresa no permite bibliotecas de terceros, solo puede usar Deflate. También debe considerar de manera integral cuántos recursos de CPU gratuitos hay y dónde se almacenan los datos comprimidos. Por ejemplo, si desea almacenar datos comprimidos en HDD, el rendimiento anterior de 100 MB/s no es útil para usted (suponiendo que su archivo sea lo suficientemente grande): la velocidad de HDD se convertirá en un cuello de botella. Si se emite el mismo archivo al disco duro SSD, incluso el LZ4 aparecerá demasiado lento frente a él. Si desea comprimir los datos primero y luego enviarlos a la red, es mejor elegir LZ4, porque el rendimiento de compresión de DeFlate75Mb/S es realmente pequeño en comparación con el rendimiento de 125 MB/s (por supuesto, sé que también hay un baotou en el tráfico de la red, pero incluso si se incluye, el espacio es bastante considerable).
Resumir
Si cree que la compresión de datos es muy lenta, puede considerar la implementación LZ4 (rápida), que puede lograr velocidades de compresión de texto de aproximadamente 320 MB/seg; dicha velocidad de compresión no debe percibirse para la mayoría de las aplicaciones.
Si está limitado a no poder usar bibliotecas de terceros o simplemente desea tener una solución de compresión ligeramente mejor, puede considerar usar JDK DeFlate (LVL = 1) para codificar y decodificar: el mismo archivo puede comprimir el mismo archivo a 75Mb/s.
código fuente
Código fuente de prueba de compresión de Java