Créer et comparer des archives tar avec Zstd et LZ4
Faites passer la sortie de tar par zstd ou lz4 pour choisir explicitement le compresseur et ses réglages.
La procédure ci-dessous crée les deux archives, restaure leurs fichiers et mesure le temps de
compression et la taille sur la même entrée. Chaque archive ne reçoit un nouveau nom qu’après la
réussite de la compression ; relancer une commande ne peut pas remplacer une archive existante.
Vérifier vos outils
Utilisez Linux avec Bash, GNU tar, GNU coreutils, diff, Python 3.9 ou plus récent, Zstd et LZ4 1.10.0 ou
plus récent. Les commandes ci-dessous ont été testées avec GNU tar 1.35, Bash 5.3.15, Zstd 1.5.7,
LZ4 1.10.0 et Python 3.14.7. Installez les outils manquants avec le gestionnaire de paquets de votre
distribution, puis vérifiez :
tar --version
bash --version
zstd --version
lz4 --version
python3 --version
ln --version
L’implémentation de tar compte. GNU tar prend en charge --zstd,
mais n’a pas d’option --lz4. bsdtar prend en charge les deux options lors de la création d’archives
et détecte ces formats à la lecture. Cette procédure utilise GNU tar et des compresseurs séparés ;
ce n’est pas une recette pour un shell macOS/BSD, notamment parce que ln -T ci-dessous est une option GNU.
Créer un répertoire source reproductible
Exécutez chaque bloc Bash depuis le même répertoire de départ. Les parenthèses gardent cd et les
options du shell locales à chaque bloc. La préparation refuse de réutiliser un répertoire tar-lab
existant ; elle ne peut donc pas écraser une expérience antérieure.
(
set -euo pipefail
mkdir tar-lab
cd -- tar-lab
mkdir project
python3 - <<'PY'
from pathlib import Path
import random
project = Path("project")
with (project / "records.csv").open("xb") as output:
for index in range(1_000_000):
output.write(f"{index:08d},user-{index % 1000:04d},GET,/assets/app.js,200\n".encode())
(project / "binary.bin").write_bytes(random.Random(0).randbytes(8 * 1024 * 1024))
(project / "empty.txt").touch()
(project / "omit.log").write_text("Exclude this log.\n", encoding="utf-8")
PY
)
Ce jeu de test combine du texte structuré avec des octets pseudo-aléatoires déterministes et un fichier vide. Ne modifiez pas la source pendant son archivage. Une commande tar réussie ne constitue pas un instantané cohérent de fichiers qu’une application est en train de modifier.
Créer des archives sans remplacer les sorties précédentes
Les deux exemples archivent project/ et omettent les noms correspondant à *.log, y compris les journaux
imbriqués. Le motif entre guillemets parvient à tar sans expansion par le shell. L’option -c de tar
crée l’archive ; -f - l’envoie sur la sortie standard. Le compresseur écrit dans un nouveau
répertoire temporaire à côté de l’archive finale, hors de l’arborescence source.
pipefail fait échouer le pipeline si le producteur tar échoue, même si le compresseur réussit.
Il ne protège pas un fichier ouvert par une redirection du shell : > peut tronquer une ancienne
sauvegarde avant l’exécution de tar. Ici, seule la sortie temporaire est redirigée. Une fois le test
d’intégrité du compresseur réussi, GNU ln -T donne à ces
octets complets leur nom définitif. Sans -f, il refuse un fichier, un répertoire ou un lien symbolique
existant. Les noms temporaire et définitif partagent un même système de fichiers, ce qu’exigent les
liens physiques. Le trap de sortie supprime le répertoire temporaire en cas de réussite normale ou
d’échec.
Créer l’archive Zstd
Utilisez le niveau trois et un seul thread de compression. Dans le
manuel de la CLI Zstd, -T1 signifie un seul
thread de compression en plus des E/S ; c’est différent de --single-thread.
(
set -euo pipefail
cd -- tar-lab
stage=$(mktemp -d .zstd.XXXXXX)
trap 'rm -rf -- "$stage"' EXIT
tar --exclude='*.log' -cf - project | zstd -3 -T1 -c > "$stage/archive"
zstd -t "$stage/archive"
ln -T -- "$stage/archive" project.tar.zst
printf 'Created project.tar.zst\n'
)
Créer l’archive LZ4
Utilisez ici aussi le niveau un et un seul thread de compression. LZ4 1.10.0 a ajouté la compression
multithread ; son manuel de la CLI documente -T1.
Les versions antérieures de LZ4 ne prennent pas en charge cette option.
(
set -euo pipefail
cd -- tar-lab
stage=$(mktemp -d .lz4.XXXXXX)
trap 'rm -rf -- "$stage"' EXIT
tar --exclude='*.log' -cf - project | lz4 -1 -T1 -c > "$stage/archive"
lz4 -t "$stage/archive"
ln -T -- "$stage/archive" project.tar.lz4
printf 'Created project.tar.lz4\n'
)
En cas de réussite, la commande se termine par le message Created correspondant. Une nouvelle
exécution renvoie un statut non nul et laisse intacts les octets de l’archive existante, même si
project/ a disparu. Pour créer une autre archive, choisissez un autre nom de fichier final ; ne
supprimez pas une sauvegarde antérieure uniquement pour faire réussir une nouvelle exécution.
Un arrêt forcé du processus ou une coupure de courant peut laisser des fichiers temporaires. Ces
commandes n’assurent pas la durabilité en cas de plantage.
Restaurer et comparer les fichiers
Un test d’intégrité vérifie le flux compressé, mais n’établit pas qu’il contient les fichiers que vous vouliez sauvegarder. Restaurez dans un nouveau répertoire et comparez-le avec la source inchangée :
(
set -euo pipefail
cd -- tar-lab
zstd -t project.tar.zst
mkdir restored-zstd
zstd -dc project.tar.zst | tar -xf - -C restored-zstd
diff -r --exclude='*.log' project restored-zstd/project
printf 'Zstd restore matches the source.\n'
)
(
set -euo pipefail
cd -- tar-lab
lz4 -t project.tar.lz4
mkdir restored-lz4
lz4 -dc project.tar.lz4 | tar -xf - -C restored-lz4
diff -r --exclude='*.log' project restored-lz4/project
printf 'LZ4 restore matches the source.\n'
)
Aucune sortie de diff ne doit apparaître, et le message de réussite correspondant doit s’afficher.
Les arborescences restaurées contiennent records.csv, binary.bin et empty.txt ; omit.log est absent.
Cette comparaison porte sur les noms et le contenu des fichiers, pas sur les propriétaires, les
permissions ni l’ensemble des métadonnées du système de fichiers. Une nouvelle exécution de la
restauration refuse le répertoire de restauration existant. Un échec d’extraction peut laisser une
nouvelle arborescence partielle ; ne considérez donc la restauration comme réussie qu’une fois la
comparaison validée. Utilisez ces commandes pour des archives que vous avez créées et auxquelles
vous faites confiance.
Mesurer le compromis sur le même flux tar
Exécutez ce bloc depuis le répertoire de départ après la préparation. Il crée un tar temporaire non compressé avec un ordre des membres, des horodatages et une propriété fixes, puis mesure chaque compresseur et décodeur. Chaque codec bénéficie d’une exécution de préchauffage et de cinq exécutions mesurées. Chaque tar décodé doit être identique octet pour octet à l’entrée. Seuls les fichiers temporaires privés du benchmark sont réutilisés ; il ne touche ni à vos archives ni à vos restaurations.
python3 - <<'PY'
import hashlib
from pathlib import Path
from statistics import median
import subprocess
from tempfile import TemporaryDirectory
from time import perf_counter
def digest(path):
return hashlib.sha256(path.read_bytes()).digest()
def timed(command, source, destination):
with source.open("rb") as stdin, destination.open("wb") as stdout:
started = perf_counter()
subprocess.run(command, stdin=stdin, stdout=stdout, check=True)
return perf_counter() - started
with TemporaryDirectory(prefix="benchmark-", dir="tar-lab") as temporary:
work = Path(temporary)
raw = work / "input.tar"
with raw.open("xb") as output:
subprocess.run([
"tar", "--sort=name", "--mtime=@0", "--owner=0", "--group=0",
"--numeric-owner", "--exclude=*.log", "-cf", "-", "project",
], cwd="tar-lab", stdout=output, check=True)
expected = digest(raw)
raw_size = raw.stat().st_size
print(f"tar bytes: {raw_size}")
print("codec bytes compressed/tar compress_s decompress_s")
for codec, options in [("zstd", ["-3", "-T1"]), ("lz4", ["-1", "-T1"])]:
packed, decoded = work / codec, work / "decoded.tar"
samples = []
for run in range(6):
compress = timed([codec, *options, "-q", "-c"], raw, packed)
decompress = timed([codec, "-q", "-dc"], packed, decoded)
if digest(decoded) != expected:
raise RuntimeError(f"{codec} changed the tar bytes")
if run > 0:
samples.append((compress, decompress))
size = packed.stat().st_size
print(f"{codec} {size} {size / raw_size:.3f} "
f"{median(c for c, d in samples):.4f} {median(d for c, d in samples):.4f}")
PY
Les temps incluent le démarrage des sous-processus et les E/S sur fichiers locaux, mais excluent la création du tar, l’extraction des membres et la comparaison SHA-256. Ce sont des mesures à cache chaud, pas un test de débit disque. Le ratio correspond aux octets compressés divisés par les octets du tar non compressé ; plus il est petit, mieux c’est.
Le 24 septembre 2026, le jeu de test a produit un tar de 50 401 280 octets sous Linux x86-64 avec un processeur AMD Ryzen AI Max+ 395. Avec les versions d’outils indiquées ci-dessus, les temps écoulés médians étaient :
| Réglages du compresseur | Octets de l’archive | Compressé/tar | Compression | Décompression |
|---|---|---|---|---|
Zstd -3 -T1 | 8 946 393 | 0,178 | 0,0281 s | 0,0124 s |
LZ4 -1 -T1 | 15 830 951 | 0,314 | 0,0519 s | 0,0332 s |
Zstd a produit l’archive la plus petite et a terminé les deux opérations plus rapidement lors de cette exécution. La structure CSV répétée et les temps d’une seule machine n’établissent pas de classement universel. Remplacez le jeu de test par des fichiers représentatifs avant de choisir un compresseur pour votre charge de travail, et vérifiez que le destinataire peut décoder le format. Les exécutions courtes sont sensibles aux autres activités de la machine.
Utiliser le résultat dans un processus de sauvegarde
Séparez la compression de la sélection des fichiers et de l’historique des sauvegardes.
Sélectionner les fichiers récemment modifiés avec find ne suit pas les suppressions. Pour une
chaîne de modifications restaurable, suivez le
guide de sauvegarde incrémentale de GNU tar.
Ne planifiez une sauvegarde qu’après avoir testé ses chemins d’échec et de restauration, et
conservez les archives antérieures réussies jusqu’à ce que votre politique de rétention en autorise
la suppression.
Pour les transferts SSH, copiez une archive terminée et vérifiez-la à destination. Si vous découpez une archive en plusieurs parties, conservez leur ordre exact pour le réassemblage et vérifiez le fichier réassemblé avant de le restaurer. Ni le transport ni le découpage ne modifient le choix du compresseur mesuré ici.
