Je me demandais s'il était possible de faire quelque chose de plus efficace que la décompression du début du fichier jusqu'au point. Il semble que la réponse soit non. Cependant, sur certains processeurs (Skylake)zcat | tail
ne fait pas monter le CPU à la vitesse d'horloge complète. Voir ci-dessous. Un décodeur personnalisé pourrait éviter ce problème et enregistrer les appels système d'écriture de tuyau, et peut-être être 10% plus rapide. (Ou ~ 60% plus rapide sur Skylake si vous ne modifiez pas les paramètres de gestion de l'alimentation).
Le mieux que vous puissiez faire avec un zlib personnalisé avec skipbytes
fonction serait d'analyser les symboles dans un bloc de compression pour arriver à la fin sans faire le travail de reconstruction réelle du bloc décompressé. Cela pourrait être significativement plus rapide (probablement au moins 2x) que d'appeler la fonction de décodage régulière de zlib pour écraser le même tampon et avancer dans le fichier. Mais je ne sais pas si quelqu'un a écrit une telle fonction. (Et je pense que cela ne fonctionne réellement que si le fichier a été écrit spécialement pour permettre au décodeur de redémarrer à un certain bloc).
J'espérais qu'il y avait un moyen de sauter les blocs Deflate sans les décoder, car ce serait beaucoup plus rapide. L'arbre Huffman est envoyé au début de chaque bloc, vous pouvez donc décoder à partir du début de n'importe quel bloc (je pense). Oh, je pense que l'état du décodeur est plus que l'arbre de Huffman, c'est aussi les 32 ko de données décodées précédents, et ce n'est pas réinitialisé / oublié par-delà les limites des blocs par défaut. Les mêmes octets peuvent continuer à être référencés à plusieurs reprises, ils ne peuvent donc apparaître littéralement qu'une seule fois dans un fichier compressé géant. (Par exemple, dans un fichier journal, le nom d'hôte reste probablement "chaud" dans le dictionnaire de compression tout le temps, et chaque instance de celui-ci fait référence au précédent, pas au premier).
Le zlib
manuel indique que vous devez utiliser Z_FULL_FLUSH
lors de l'appel deflate
si vous voulez que le flux compressé soit recherché à ce point. Il "réinitialise l'état de compression", donc je pense que sans cela, les références en arrière peuvent aller dans le (s) bloc (s) précédent (s). Donc, à moins que votre fichier zip n'ait été écrit avec des blocs de vidage occasionnels (comme chaque 1G ou quelque chose aurait un impact négligeable sur la compression), je pense que vous auriez à faire plus de travail de décodage jusqu'au point que vous vouliez qu'au départ. en pensant. Je suppose que vous ne pouvez probablement pas commencer au début d'un bloc.
Le reste de ceci a été écrit alors que je pensais qu'il serait possible de trouver juste le début du bloc contenant le premier octet que vous voulez, et de décoder à partir de là.
Mais malheureusement, le début d'un bloc Deflate n'indique pas sa durée , pour les blocs compressés. Les données incompressibles peuvent être codées avec un type de bloc non compressé qui a une taille de 16 bits en octets à l'avant, mais pas les blocs compressés: la RFC 1951 décrit le format de manière assez lisible . Les blocs avec codage Huffman dynamique ont l'arborescence à l'avant du bloc (donc le décompresseur n'a pas à chercher dans le flux), donc le compresseur doit avoir gardé le bloc entier (compressé) en mémoire avant de l'écrire.
La distance de référence arrière maximale n'est que de 32 Ko, de sorte que le compresseur n'a pas besoin de conserver beaucoup de données non compressées en mémoire, mais cela ne limite pas la taille du bloc. Les blocs peuvent avoir plusieurs mégaoctets. (Ceci est suffisamment grand pour que le disque en vaille la peine, même sur un lecteur magnétique, par rapport à la lecture séquentielle en mémoire et simplement à l'omission de données dans la RAM, s'il était possible de trouver la fin du bloc actuel sans l'analyse).
zlib crée des blocs le plus longtemps possible:
selon Marc Adler , zlib ne démarre un nouveau bloc que lorsque le tampon de symboles se remplit, ce qui avec le paramètre par défaut est 16 383 symboles (littéraux ou correspondances)
J'ai compressé la sortie de seq
(ce qui est extrêmement redondant et donc probablement pas un excellent test), mais pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
cela ne fonctionne qu'à ~ 62 Mio / s de données compressées sur un Skylake i7-6700k à 3,9 GHz, avec DDR4-2666 RAM. Cela représente 246 Mo / s de données décompressées, ce qui représente un changement important par rapport à une memcpy
vitesse de ~ 12 Gio / s pour des tailles de bloc trop grandes pour tenir dans le cache.
(Avec energy_performance_preference
la valeur par défaut balance_power
au lieu de balance_performance
, le gouverneur de processeur interne de Skylake décide de ne fonctionner qu'à 2,7 GHz, ~ 43 Mio / s de données compressées. J'utilise sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
pour le modifier. Probablement, ces appels système fréquents ne ressemblent pas à de vrais CPU-liés travailler à l'unité de gestion de l'énergie.)
TL: DR: zcat | tail -c
est lié au processeur même sur un processeur rapide, sauf si vous avez des disques très lents. gzip a utilisé 100% du processeur sur lequel il fonctionnait (et a exécuté 1,81 instructions par horloge, selon perf
), et a tail
utilisé 0,162 du processeur sur lequel il fonctionnait (0,58 IPC). Le système était par ailleurs essentiellement inactif.
J'utilise Linux 4.14.11-1-ARCH, qui a KPTI activé par défaut pour contourner Meltdown, donc tous ces write
appels système gzip
sont plus chers qu'avant : /
Avoir la recherche intégrée à unzip
ou zcat
(mais toujours en utilisant la zlib
fonction de décodage régulière ) permettrait d'économiser toutes ces écritures de pipe et de faire fonctionner les processeurs Skylake à pleine vitesse d'horloge. (Ce downclocking pour certains types de charge est unique à Intel Skylake et versions ultérieures, qui déchargent la prise de décision de fréquence du processeur à partir du système d'exploitation, car ils ont plus de données sur ce que fait le processeur et peuvent augmenter / diminuer plus rapidement. normalement bon, mais ici, Skylake ne monte pas à pleine vitesse avec un réglage de gouverneur plus conservateur).
Aucun appel système, simplement réécrire un tampon qui tient dans le cache L2 jusqu'à ce que vous atteigniez la position d'octet de départ que vous souhaitez, ferait probablement au moins une différence de quelques%. Peut-être même 10%, mais je fais juste des chiffres ici. Je n'ai pas profilé zlib
en détail pour voir la taille d'une empreinte de cache, et combien le vidage TLB (et donc le vidage uop-cache) sur chaque appel système fait mal avec KPTI activé.
Il existe quelques projets logiciels qui ajoutent un index de recherche au format de fichier gzip . Cela ne vous aide pas si vous ne parvenez pas à faire en sorte que quelqu'un génère des fichiers compressés recherchés pour vous, mais d'autres futurs lecteurs pourraient en bénéficier.
Vraisemblablement, aucun de ces projets n'a de fonction de décodage qui sait ignorer un flux Deflate sans index, car ils ne sont conçus pour fonctionner que lorsqu'un index est disponible.