J'ai découvert l'été dernier les commandes jpegoptim et zopflipng, elles se proposent chacune de recompresser, sans perte de qualité, des images JPEG et PNG. On peut espérer jusqu'à 25% de gain de place pour les photos (floues et granuleuses) prises (de travers) depuis un smartphone Android (pendant un concert...). Un quart de place, sur le stockage limité de ce genre de périphérique, ça peut être appréciable.
Par contre, s'il s’agit de reprendre toutes ses photos rangées automatiquement dans des dossiers horodatés depuis 15 ans, y'a moyen d'y passer un brave moment, d'autant que ces commandes annoncent la couleur : elles sont lentes, car il faut du temps pour réussir à faire mieux que les encodeurs JPEG et PNG que l'on rencontre généralement…
Voyons comment lancer efficacement ses optimisations d'images, dans un article digne d'un Inception ou d'un Existenz au cinéma, où l'on descendra dans la meta-optimisation du processus, en recompilant entre autres les commandes, pour gagner aussi sur le temps d'exécution.
1. Optimiser les images
jpegoptim est une commande développée depuis 15 ans et présente de longue date dans les dépôts Debian. Elle optimise les tables de Huffman utilisées pour la compression des JPEG en testant différents paramétrages, et garde, pour chaque image, le meilleur résultat. Il est également possible de lui demander une réduction jusqu'à la taille voulue, et l'algorithme fera alors de son mieux avec cette contrainte pour limiter la casse. Pour l'installer :
apt install jpegoptim
zopfli est lui un algorithme de compression relativement récent (le premier commit du dépôt Git du projet date d'il y a 4 ans). La commande zopflipngn'est d'ailleurs pas encore présente dans les dépôts Debian, et il faut aller se la cloner depuis le dépôt Git du projet correspondant de Google, libre sur GitHub : https://github.com/google/zopfli.
Puis, en se plaçant à la racine du dépôt, un petit :
make zopflipng
Vous compilera le tout.
La particularité de l'algorithme zopfli est de pouvoir être décompressé par les algorithmes deflate, gzip et zlib déjà largement implémentés et répandus (dans les navigateurs web, par défaut pour les logs dans GNU/Linux et donc également pour les fichiers PNG). Il est également amusant de noter que zopfli veut dire « petite tresse au beurre » en suisse allemand, et qu'il s'agit d'un clin d'œil au format de compression Brotli (également développé par Google et aux performances proches du LZMA), portant lui aussi le nom d'une viennoiserie suisse.
Pourquoi réinventer un algorithme de compression, et s'appliquer à le rendre compatible avec les outils de décompression existants ? Tout simplement parce quand on est Google, gagner 1% sur la taille d'un logo sans perte de qualité, ça représente tout de suite des économies astronomiques en terme de bande passante, et puis c'est toujours ça de pris sur le temps de chargement des pages. Or là, on parle de 5 à 15% de gain en pratique sur les PNG d'un site web.
2. Rentrons dans le vif du sujet
Maintenant qu'on a fixé le décor, voyons comment lancer ces commandes de manière efficace et optimisons, non plus la taille d'images, mais l'exécution de ces commandes. Pour rentrer dans le vif du sujet, on peut lancer ces re-compresseurs d'images à l'aide des commandes à tuyau suivantes, pour faire du traitement générique de fichiers par lot, parallélisé.
jpeqoptim :
find . -regextype posix-extended -iregex '.*(jpeg|jpg)' -print0 | xargs -0 -P $((`nproc` - 1)) jpegoptim -t
zopflipng :
find . -regextype posix-extended -iregex '.*png' -print0 | xargs -0 -P $((`nproc` - 1)) -I {} zopflipng -m --lossy_8bit --lossy_transparent -y {} {}
Ici, dans les deux cas la commande find se charge de trouver les fichiers et les signale à la commande xargs. On s'appuie, pour ce faire, sur une expression régulière insensible à la casse, histoire de ne laisser aucun fichier en réchapper. Même les noms de fichiers contenant des espaces sont supportés, grâce au couple d'arguments -print0 côté find et -0 côté xargs. Cette dernière va, elle, lancer les traitements, autant que le nombre de processeurs trouvés (moins un, histoire de pouvoir continuer à utiliser la machine ; avec l'hyperthreading d'Intel, un « divisé par deux » ne serait pas pire à ma connaissance) c'est le rôle de l'argument -P que l'on renseigne avec le traitement arithmétique shell $((`nproc` - 1)) sur le résultat de la commande nproc qui nous retourne le nombre de microprocesseurs du système. Enfin, dans le cas de la commande zopflipng, on est obligé de passer par un motif de remplacement du nom de fichier dans la commande, ici {} déclaré par l'argument -I, car la commande ne sait pas remplacer directement l'ancien fichier par le nouveau, ce qui est au contraire au comportement par défaut de jpegoptim (et peut surprendre…).
Du coup ça se lance, ça explore le disque depuis le dossier courant, occupe bien le CPU et libère de l'espace disque. Bien, mais ça prend plein de temps, or on peut accélérer les choses.
3. apt-build
Tout d'abord, on peut recompiler la commande qui compresse les images avec apt-build quand on est sous Debian. Pour ce faire, on commence par un petit :
apt install apt-build
Le process post-install de configuration demande pour quelle architecture compiler, or GCC connaît déjà votre processeur mieux que vous, répondre -march=natives'impose donc, puis ça nous demande quel niveau d'optimisation choisir pour la compilation, en indiquant que le niveau le plus avancé n'est pas fiable, restons donc au niveau intermédiaire…
On peut ensuite, lancer :
apt-build install jpegoptim
Attendre quelques minutes que ça compile, et si tout se passe bien, notamment parce que vous avez gardé les lignes qui commencent par deb-src dans votre fichier /etc/apt/source.lst et qui ne servent à ma connaissance qu'à ça, vous vous retrouvez avec un paquet .deb local, créé sur votre machine par apt-build et installé dans la foulée. Un petit tour dans Synaptic montre que la version du paquet en question est suffixée par un -aptbuild1, preuve qu’il s’agit bien du fruit de votre compilation.
Pour que tout se passe bien, je suis allé chercher un épinglage adéquat pour ce nouveau dépôt de paquets locaux (Siltaar étant mon pseudo) :
cat /etc/apt/preferences.d/50Siltaar
Package: *
Pin: release o=apt-build
Pin-Priority: 990
Package: *
Pin: release a=jessie-backports
Pin-Priority: 500
Package: *
Pin: release a=stable/updates
Pin-Priority: 500
Package: *
Pin: release a=stable
Pin-Priority: 500
On constate qu’ici tout tient en équilibre parce qu'il n'y a pas de dépôt unstable en jeu. Il faudrait donner à un tel dépôt une priorité inférieure au stable pour utiliser un paquet unstabletout seul sans contaminer le reste du système un peu plus à chaque mise à jour .
On peut alors s'attendre à des gains de performance de 5% à 15% par rapport à la version précompilée (qui ne tire pas spécifiquement parti des dernières fonctionnalités de votre microprocesseur). Pour tendre vers les 15% de gain, rien ne vous empêche de compiler aussi la commande xargs, la commande findet même la libc6 tant qu'on y est !
En regardant ce qui tourne sur ma Debian/Xfce4, j'ai constaté qu'il y a plusieurs icônes de ma barre de tâches qui cachent en fait un script Python lancé via python3. Python est recompilable également :
apt-build install python3 libpython3.4 libpython3.4-minimal libpython3.4-stdlib
aptitude markauto python3 libpython3.4 libpython3.4-minimal libpython3.4-stdlib
La deuxième ligne sert ici à remarquer comme « installé automatiquement » les paquets recompilés, qui sinon perdent ce statut de par l'attention particulière qu'on vient de leur porter.
Il faut compter une heure de compilation par paquet sur une machine à 3024 bogomips et 8Go RAM en 64bits...
Une fois lancés, on aurait bien recompilé qemu-kvm, firefox-esr ou encore aptitude, mais la compilation échoue dans ces cas-là sans autres interventions. Ce sont spontanément les programmes qui me viennent à l'esprit en matière de paquets recompilables et qui font attendre dans la vie de tous les jours. Leur recompilation méritera probablement un article à part entière...
4. prelink
Ce n'est pas tout, on peut encore faire gagner environ 10% de performance à notre jpegoptim, en pré-liant les bibliothèques dynamiques de fonction que la commande commence par chercher à son lancement. Il existe en effet une commande prelink, disponible dans le paquet Debian éponyme, dédiée à cet usage :
prelink jpegoptim find xargs
prelinkest une commande rapide très polie et pleine d'options. Elle permet notamment de revenir en arrière avec l'argument --undo, de vérifier qu'un programme a déjà été pré-lié (--verify) et de mettre en place des listes noires de bibliothèques à exclure du traitement (si on souhaite garder quelques bibliothèques en dynamiques).
Conclusion
Au travers de cet exemple pratique, ce sont des techniques éminemment génériques qui ont été présentées. Bien sûr, avant d'avoir regagné en économie de temps d'exécution, rien que le temps passé à compiler les paquets, il faut en avoir du JPEG à recompresser, mais ces astuces pour accélérer la vitesse d'exécution de programmes compilés sont réutilisables dans de nombreux cas de figure (encodage audio ou vidéo, compression ou conversion de fichiers en ligne de commandes...) et donc avec bien d'autres commandes. De plus, cet article abordait des sujets variés en autant d’invitations à explorer plus avant les problématiques abordées : pile d’exécution C et bibliothèques dynamiques, gestion des dépôts de paquets dans Debian et épinglage, nouveaux algorithmes de compression de données... J’espère que vous y aurez appris au moins quelques astuces. Si vous en avez appris beaucoup, voire pas assez, rappelez-vous qu’apprendre à se servir d’un GNU/Linux est un long chemin, et que même avec le temps, on ne maîtrise pas GNU/Linux, on devient humble.