web-dev-qa-db-fra.com

Pourquoi les données encodées en base64 sont-elles si compressées?

Je compressais récemment des fichiers et j'ai remarqué que les données encodées en base64 semblaient compresser très mal. Voici un exemple:

  • Fichier d'origine: 429,7 Mio
  • compresser via xz -9:
    13,2 MiB / 429,7 MiB = 0,0314,9 MiB/s1:28
  • base64 le et compresse via xz -9:
    26,7 MiB / 580,4 MiB = 0,0462,6 MiB/s3:47
  • base64 le fichier xz compressé d’origine:
    17,8 MiB _ presque en un rien de temps = l'attendu 1.33x augmenter en taille

Donc, ce qui peut être observé est que:

  • xz compresse vraiment bien
  • les données encodées en base64 ne se compressent pas bien, elles sont 2 fois plus volumineuses que le fichier compressé non codé
  • base64-then-compress est nettement pire et plus lent que compress-then-base64

Comment se peut-il? Base64 est un algorithme réversible sans perte. Pourquoi affecte-t-il autant la compression? (J'ai essayé avec gzip aussi, avec des résultats similaires).

Je sais que cela n'a pas de sens de base64-then-compress un fichier, mais la plupart du temps, on n'a pas le contrôle sur les fichiers d'entrée Et j'aurais pensé que, puisque la densité d'informations réelle (ou le nom de fichier), un fichier codé en base64 serait presque identique à la version non codée, et donc compressible de la même manière.

42
Stefan Seidel

La plupart des algorithmes de compression génériques fonctionnent avec granularité sur un octet.

Considérons la chaîne suivante:

"XXXXYYYYXXXXYYYY"
  • Un algorithme Run-Length-Encoding dira: "soit 4 'X', suivi de 4 'Y', suivi de 4 'X', suivi de 4 'Y'"
  • Un algorithme de Lempel-Ziv dira: "C'est la chaîne 'XXXXYYYY', suivie de la même chaîne: remplaçons donc la deuxième chaîne par une référence à la première."
  • Un algorithme de codage de Huffman dira: "Il n'y a que 2 symboles dans cette chaîne, je ne peux donc utiliser qu'un bit par symbole."

Encodons maintenant notre chaîne en Base64. Voici ce que nous obtenons:

"WFhYWFlZWVlYWFhYWVlZWQ=="

Tous les algorithmes disent maintenant: "Quel genre de gâchis est-ce?" . Et ils ne risquent pas de compresser très bien cette chaîne.

Pour rappel, Base64 fonctionne essentiellement en réencodant des groupes de 3 octets dans (0 ... 255) en groupes de 4 octets dans (0 ... 63):

Input bytes    : aaaaaaaa bbbbbbbb cccccccc
6-bit repacking: 00aaaaaa 00aabbbb 00bbbbcc 00cccccc

Chaque octet de sortie est ensuite transformé en un caractère imprimable ASCII. Par convention, ces caractères sont (ici avec une marque tous les 10 caractères):

0         1         2         3         4         5         6
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

Par exemple, notre exemple de chaîne commence par un groupe de trois octets égal à 0x58 au format hexadécimal (code ASCII du caractère "X"). Ou en binaire: 01011000. Appliquons le codage Base64:

Input bytes      : 0x58     0x58     0x58
As binary        : 01011000 01011000 01011000
6-bit repacking  : 00010110 00000101 00100001 00011000
As decimal       : 22       5        33       24
Base64 characters: 'W'      'F'      'h'      'Y'
Output bytes     : 0x57     0x46     0x68     0x59

Fondamentalement, le modèle "3 fois l'octet 0x58" qui était évident dans le flux de données d'origine n'est plus évident dans le flux de données codé, car nous avons fractionné les octets en paquets de 6 bits et les avons mappés à de nouveaux octets qui apparaissent maintenant. être aléatoire.

Ou en d'autres termes: nous avons rompu l'alignement d'octet original sur lequel la plupart des algorithmes de compression reposent.

Quelle que soit la méthode de compression utilisée, elle a généralement un impact important sur les performances de l’algorithme. C'est pourquoi vous devriez toujours compresser en premier et encoder en second.

Ceci est encore plus vrai pour le chiffrement: compresser en premier, chiffrer en second.

EDIT - Une note à propos de LZMA

Comme MSalters l’a remarqué, LZMA - que xz utilise actuellement - travaille sur des flux de bits plutôt que sur des flux d’octets.

Néanmoins, cet algorithme souffrira également du codage Base64 d’une manière qui est essentiellement cohérente avec ma description précédente:

Input bytes      : 0x58     0x58     0x58
As binary        : 01011000 01011000 01011000
(see above for the details of Base64 encoding)
Output bytes     : 0x57     0x46     0x68     0x59
As binary        : 01010111 01000110 01101000 01011001

Même en travaillant au niveau des bits, il est beaucoup plus facile de reconnaître un motif dans la séquence binaire d'entrée que dans la séquence binaire de sortie.

89
Arnauld

La compression est nécessairement une opération qui agit sur plusieurs bits. Essayer de compresser un "0" et un "1" ne présente aucun avantage. Malgré tout, la compression fonctionne généralement sur un ensemble limité de bits à la fois. L'algorithme LZMA dans xz ne prendra pas en compte tous les 3,6 milliards de bits à la fois. Il examine des chaînes beaucoup plus petites (<273 octets).

Examinons maintenant le codage en base 64: il remplace un mot de 3 octets (24 bits) par un mot de 4 octets, en utilisant seulement 64 des 256 valeurs possibles. Cela vous donne la croissance x 1,33.

Il est maintenant assez clair que cette croissance doit entraîner la croissance de certaines chaînes au-delà de la taille maximale de la chaîne du codeur. Cela les rend non plus compressés en une seule sous-chaîne, mais bien en deux sous-chaînes distinctes.

Comme vous avez un lot de compression (97%), il semble que vous ayez le cas où des sous-chaînes d'entrée très longues sont compressées dans leur ensemble. cela signifie que de nombreuses sous-chaînes seront également développées en base 64 au-delà de la longueur maximale que l'encodeur peut traiter.

8
MSalters