Cet article comparera les performances de plusieurs algorithmes de compression couramment utilisés. Les résultats montrent que certains algorithmes fonctionnent toujours correctement sous des contraintes de processeur extrêmement exigeantes.
Les comparaisons de l'article comprennent:
JDK GZIP - Il s'agit d'un algorithme lent avec un rapport de compression élevé, et les données compressées conviennent à une utilisation à long terme. java.util.zip.gzipinputStream / gzipOutputStream dans JDK est la mise en œuvre de cet algorithme.
JDK Deflate - Il s'agit d'un autre algorithme dans JDK (cet algorithme est utilisé dans les fichiers ZIP). Ce qui le rend différent de GZIP, c'est que vous pouvez spécifier le niveau de compression de l'algorithme afin que vous puissiez équilibrer le temps de compression et la taille du fichier de sortie. Les niveaux facultatifs sont 0 (non compressés) et 1 (compressé rapide) à 9 (compressé lent). Sa mise en œuvre est java.util.zip.deflateroutputStream / inflaterinputStream.
La mise en œuvre Java de l'algorithme de compression LZ4 - il s'agit de la vitesse de compression la plus rapide parmi les algorithmes introduits dans cet article. Par rapport au dégonflage le plus rapide, ses résultats de compression sont légèrement pires.
Snappy - Il s'agit d'un algorithme de compression très populaire développé par Google. Il vise à fournir des algorithmes de compression avec un rapport de vitesse et de compression relativement bon.
Test de compression
Il m'a également fallu beaucoup de temps pour découvrir quels fichiers conviennent aux tests de compression de données et exister sur la plupart des ordinateurs de développeurs Java (je ne veux pas que vous ayez quelques centaines de mégaoctets de fichiers pour exécuter ce test). Enfin, je pensais que la plupart des gens devraient avoir la documentation pour JDK installée localement. Par conséquent, j'ai décidé de fusionner l'intégralité du répertoire Javadoc en un seul fichier - épisser tous les fichiers. Cela peut être fait facilement avec la commande TAR, mais tout le monde n'est pas un utilisateur Linux, j'ai donc écrit un programme pour générer ce fichier:
classe publique InputGenerator {chaîne finale statique privée javadoc_path = "your_path_to_jdk / docs"; public static final file_path = new File ("your_output_file_path"); statique {try {if (! file_path.exists ()) makejavadocfile (); } catch (ioException e) {e.printStackTrace (); }} private static void makejavadocfile () lève ioException {try (outputStream os = new buffredOutputStream (new FileOutputStream (file_path), 65536)) {appendDir (os, nouveau file (javadoc_path)); } System.out.println ("fichier javadoc créé"); } private static void appendDir (Final OutputStream OS, Final File root) lève ioException {for (file f: root.listFiles ()) {if (f.isdirectory ()) appendDir (os, f); else files.copy (f.topath (), os); }}}La taille de l'ensemble du fichier sur ma machine est de 354 509 602 octets (338 Mo).
test
Au début, je voulais lire le fichier entier en mémoire puis le compresser. Cependant, les résultats montrent que même les machines 4G peuvent facilement manquer d'espace mémoire de tas.
J'ai donc décidé d'utiliser le cache de fichier du système d'exploitation. Le cadre de test que nous utilisons ici est JMH. Ce fichier sera chargé dans le cache par le système d'exploitation pendant la phase d'échauffement (il sera comprimé deux fois dans la phase d'échauffement). Je vais compresser le contenu dans le flux ByteArrayOutputStream (je sais que ce n'est pas le moyen le plus rapide, mais il est relativement stable pour chaque test et ne prend pas le temps d'écrire les données compressées sur le disque), donc un espace mémoire est nécessaire pour stocker cette sortie.
Vous trouverez ci-dessous la classe de base de la classe de test. Tous les tests ne sont différents que dans les différentes implémentations du flux de sortie compressé, vous pouvez donc réutiliser cette classe de base de test et simplement générer un flux à partir de l'implémentation StreamFactory:
@OutputTimeUnit (timeunit.milliseconds) @State (scope.thread) @Fork (1) @WarmUp (iterations = 2) @measurement (iterations = 3) @BenchMarkMode (mode.SinglesHotTime) Public ClassParent {Protected Path M_InputFile; @SetUp public void setup () {m_inputFile = inputGenerator.file_path.topath (); } Interface StreamFactory {public OutputStream GetStream (Final OutputStream sous -lyingStream) lève ioException; } public int BaseBenchMark (Final StreamFactory Factory) lève 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 (); }}}Ces cas de test sont très similaires (leur code source est disponible à la fin de l'article), et un seul exemple est répertorié ici - la classe de test de JDK dégonfle;
classe publique jdkdeflateTest étend le testparent {@param ({"1", "2", "3", "4", "5", "6", "7", "8", "9"}) public int m_lvl; @Benchmark public int Deflate () lève ioException {return BaseBenchmark (new StreamFactory () {@Override public outputStream GetStream (OutputStream sous -lyingStream) lance ioException {final Deflater Deflater = New Deflater (M_lvl, true); return neflateroutPutream (UnderlyingStream, Deflater, 512); }}Résultats des tests
La taille du fichier de sortie
Tout d'abord, regardons la taille du fichier de sortie:
|| Implémentation || Size de fichier (octets) |||| gzip || 64,200,201 |||| snappy (normal) || 138 2550,196 |||| snappy (encadré) || 101 470,113 ||||| lz4 (rapide) || 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 | (lvl = 4) || 68 090 059 || || Deflate (lvl = 5) || 65,699 810 |||| Deflate (lvl = 6) || 64,200,191 ||||| accompade (lvl = 7) || 64,013 638 ||| accomplate (lvl = 7) || 64,013 638 ||| accomplate (lvl = lvl = 8) || 63,845,758 ||||| C'est deflate (lv = 9) || 63 839 200 |||
On peut voir que la taille du fichier varie considérablement (de 60 Mo à 131 Mo). Jetons un coup d'œil à la durée du temps pour différentes méthodes de compression.
Temps de compression
|| Implémentation || Compression Temps (ms) |||| snappy.framedOutput || 2264.700 |||| snappy.normaloutput || 2201.120 |||| lz4.testFastNative || 1056.326 |||| lz4.testfastUnsa Fe || 1346.835 |||| LZ4.TESTFASTSAFE || 1917.929 |||| lz4.Teshighnative || 7489.958 |||| lz4.TesthighunSafe || 10306.973 |||| lz4.Testhighsafe || 10306.973 |||| LZ4.TESTHIGHSAFE || 14413.622 |||| Seflate (lvl = 1) || 4522.644 |||| Deflate (lvl = 2) || 4726.477 |||| Deflate (lvl = 3) || 5081.934 |||| Deflate (lvl = 4) || 6739.450 ||||. || 7896.572 |||| Deflate (lvl = 6) || 9783.701 |||| Deflate (lvl = 7) || 10731.761 |||| Deflate (lvl = 8) || 14760.361 |||| Deflate (lvl = 9) || 14878.364 ||| || 10351.887 ||
Nous fusions ensuite le temps de compression et la taille du fichier dans un tableau pour compter le débit de l'algorithme et voyons quelles conclusions peuvent être tirées.
Débit et efficacité
|| Implémentation || Temps (MS) || Taille du fichier non compressé || Débit (MB / SEC) ||| Taille du fichier compressé (MB) |||| Snappy.NormalOutput || 2201.12 || 338 || 153.5581885586 || 131.8454742432 ||||| Seappy. || 149.2471409017 || 96.7693328857 |||| lz4.testfastnative || 1056.326 || 338 || 319.9769768045 || 93.7557220459 |||| LZ4.TESTSAFE || 1917.929 || 338 || 176.2317583185 || 93.7557220459 ||||| lz4.testfastUnsafe || 1346.835 || 338 || 250.9587291688 || 93.7557220459 || 38 38 || 45.1270888301 || 78.2680511475 |||| lz4.testhehighsafe || 14413.622 || 338 || 23.4500391366 || 78.2680511475 |||| lz4.testhunsafe || 10306.973 || 38 338 || 32.7933332124 || 78.2680511475 |||| Deflate (lvl = 1) || 4522.644 || 338 || 74.7350443679 || 74.7394561768 |||| Deflate (lvl = 2) || 4726.4777777 || 71.5120374012 || 71.7735290527 |||| Deflate (lvl = 3) || 5081.934 || 338 || 66.5101120951 || 69.8471069336 |||| Deflate (lvl = 4) || 6739.45 || 338 || 50.1524605124 || 64.9452209473 |||| Deflate (lvl = 5) || 7896.572 || 338 || 42.8033835442 || 62.6564025879 |||. || 34.5472536415 || 61.2258911133 ||||| Deflate (lvl = 7) || 10731.761 || 338 || 31.4952969974 || 61.0446929932 |||| 38838 3883838383838 || 22.8991689295 || 60.8825683594 |||| Deflate (lvl = 9) || 14878.364 || 338 || 22.7175514727 || 60.8730316162 ||| ° 220519251.887 || 338 || 32.6519292929 || 61.2258911133 ||
Comme on peut le voir, la plupart des implémentations sont très inefficaces: sur les processeurs Xeon E5-2650, le dégagement de haut niveau est d'environ 23 Mo / sec, et même le GZIP n'est que de 33 Mo / sec, ce qui est probablement difficile à satisfaire. Dans le même temps, l'algorithme Defalte le plus rapide peut atteindre environ 75 Mo / sec, Snappy est de 150 Mo / sec et LZ4 (Fast, Implementation JNI) peut atteindre un incroyable 320 Mo / sec!
On peut voir clairement dans le tableau qu'il existe actuellement deux implémentations qui sont désavantagées: Snappy est plus lent que LZ4 (compression rapide), et le fichier compressé est plus grand. Au contraire, LZ4 (rapport de compression élevé) est plus lent que de dégonfler des niveaux 1 à 4, et la taille du fichier de sortie est beaucoup plus grande que celle de dégonfler du niveau 1.
Par conséquent, si vous avez besoin d'effectuer une "compression en temps réel", je choisirai certainement dans la mise en œuvre de LZ4 (rapide) JNI ou le niveau 1 dégonflé. Bien sûr, si votre entreprise n'autorise pas les bibliothèques tierces, vous ne pouvez utiliser que de dégonfler. Vous devez également considérer de manière approfondie le nombre de ressources CPU gratuites et où les données compressées sont stockées. Par exemple, si vous souhaitez stocker des données compressées sur le disque dur, les performances ci-dessus de 100 Mo / s ne vous sont pas utiles (en supposant que votre fichier est suffisamment grand) - la vitesse du disque dur deviendra un goulot d'étranglement. Si le même fichier est sorti vers le disque dur SSD - même le LZ4 apparaîtra trop lentement devant lui. Si vous souhaitez d'abord compresser les données, puis l'envoyer au réseau, il est préférable de choisir LZ4, car les performances de compression de Deflate75 Mo / s sont vraiment petites par rapport au débit de 125 Mo / s (bien sûr, je sais qu'il y a aussi un baotou dans le trafic du réseau, mais même s'il est inclus, l'écart est tout à fait considérable).
Résumer
Si vous pensez que la compression des données est très lente, vous pouvez considérer l'implémentation LZ4 (rapide), qui peut atteindre des vitesses de compression de texte d'environ 320 Mo / sec - une telle vitesse de compression ne doit pas être perçue pour la plupart des applications.
Si vous êtes limité à ne pas être en mesure d'utiliser des bibliothèques tierces ou si vous souhaitez simplement avoir une solution de compression légèrement meilleure, vous pouvez envisager d'utiliser JDK Deflate (lvl = 1) pour l'encodage et le décodage - le même fichier peut compresser le même fichier à 75 Mo / s.
code source
Code source de test de compression Java