Découvrez les bibliothèques prenant en charge bzip2 dans le .NET Framework. Comparez la vitesse de traitement avec Python et bzcat.
Cet article a une version Python.
J'ai trouvé trois bibliothèques différentes prenant en charge bzip2.
Vérifiez combien de temps ces bibliothèques gèrent et déploient des flux multiples.
Les données compressées individuellement avec bzip2 et concaténées sont appelées multi-flux.
Exemple de création
$ echo -n hello | bzip2 > a.bz2
$ echo -n world | bzip2 > b.bz2
$ cat a.bz2 b.bz2 > ab.bz2
Il peut être géré tel quel avec des commandes telles que bzcat
.
$ bzcat ab.bz2
helloworld
Il est utilisé pour la compression parallèle pbzip2 et les vidages Wikipedia.
[Ajout] La même chose peut être faite avec gzip, qui est étudiée dans l'article suivant.
SharpZipLib
Prend en charge plusieurs algorithmes de compression.
L'implémentation de bzip2 est ci-dessous.
Nous avons confirmé que bzip2 peut être extrait avec seulement les 6 fichiers suivants.
Utilisez la bibliothèque obtenue par NuGet au lieu de la configuration minimale.
nuget install SharpZipLib
cp SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll .
Essayez de développer ab.bz2 créé comme exemple multi-flux.
#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System.IO
open ICSharpCode.SharpZipLib.BZip2
do
use fs = new FileStream("ab.bz2", FileMode.Open)
use bz = new BZip2InputStream(fs)
use sr = new StreamReader(bz)
printfn "%s" (sr.ReadToEnd())
hello
Seul le premier flux est traité.
En regardant BZip2InputStream.cs, il ne semble pas prendre en charge le multi-flux, vous devez donc le lire séquentiellement.
est
vrai, le flux
fs` passé une fois le traitement terminé sera fermé. La valeur par défaut est «true», spécifiez donc «false».#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System.IO
open ICSharpCode.SharpZipLib.BZip2
do
use fs = new FileStream("ab.bz2", FileMode.Open)
while fs.Position < fs.Length do
use bz = new BZip2InputStream(fs, IsStreamOwner = false)
use sr = new StreamReader(bz)
printfn "%s" (sr.ReadToEnd())
hello
world
SharpCompress
Prend en charge plusieurs algorithmes de compression.
L'implémentation de bzip2 est ci-dessous.
Ce répertoire peut être extrait et utilisé indépendamment. La seule chose est qu'il n'a pas sa propre définition de CompressionMode
, mais il remplace le type d'énumération existant.
BZip2Stream.cs (supplémentaire)
using System.IO.Compression;
Nous avons confirmé que bzip2 peut être extrait avec seulement les trois fichiers suivants.
Cependant, la classe CBZip2InputStream
est ʻinternal, elle doit donc être
publique`.
Utilisez la bibliothèque obtenue par NuGet au lieu de la configuration minimale.
nuget install sharpcompress
cp SharpCompress.0.25.1/lib/net46/SharpCompress.dll .
Essayez de développer ab.bz2 créé comme exemple multi-flux.
BZip2Stream
est un drapeau pour lire le multi-flux.#r "SharpCompress.dll"
open System.IO
open SharpCompress.Compressors
do
use fs = new FileStream("ab.bz2", FileMode.Open)
use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, true)
use sr = new StreamReader(bz)
printfn "%s" (sr.ReadToEnd())
Résultat d'exécution
helloworld
Avec la prise en charge du multi-flux, j'ai pu tout lire en même temps.
Si vous définissez l'indicateur multiflux sur false
, fs
sera fermé lorsque la fin d'un flux est atteinte. En regardant CBZip2InputStream.cs, il ne semble pas censé rester ouvert. Par conséquent, il semble que la seule façon de le lire séquentiellement est de faire quelque chose qui ignore «Dispose».
#r "SharpCompress.dll"
open System.IO
open SharpCompress.Compressors
do
let mutable ignore = true
use fs = { new FileStream("ab.bz2", FileMode.Open) with
override __.Dispose disposing = if not ignore then base.Dispose disposing }
while fs.Position < fs.Length do
use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, false)
use sr = new StreamReader(bz)
printfn "%s" (sr.ReadToEnd())
ignore <- false
Résultat d'exécution
hello
world
AR.Compression.BZip2
Contrairement à d'autres bibliothèques, il se spécialise dans bzip2. La décompression et la compression sont implémentées dans une classe, nous allons donc ignorer la configuration minimale séparément.
Il est enregistré dans NuGet.
Le partage de la bibliothèque avec Mono nécessite un peu de travail, donc cette fois je vais la construire moi-même au lieu d'utiliser NuGet.
Renommez la DLL spécifiée dans P / Invoke.
10: private const string DllName = "libbz2";
Cela utilisera libbz2.dll sur Windows et libbz2.so sur WSL. WSL référence /usr/lib/libbz2.so même s'il ne se trouve pas dans le répertoire courant.
Créez la DLL.
csc -o -out:AR.Compression.BZip2.dll -t:library -unsafe sources/AR.BZip2/*.cs
Bzip2 pour Windows utilise les binaires distribués ci-dessous.
Essayez de développer ab.bz2 créé comme exemple multi-flux.
#r "AR.Compression.BZip2.dll"
open System.IO
open System.IO.Compression
do
use fs = new FileStream("ab.bz2", FileMode.Open)
use bz = new BZip2Stream(fs, CompressionMode.Decompress, false)
use sr = new StreamReader(bz)
printfn "%s" (sr.ReadToEnd())
Résultat d'exécution
helloworld
Tous les flux ont été développés en même temps. En regardant BZip2Stream.cs, il semble qu'il ne soit pas censé être lu séquentiellement. Compte tenu de la gestion du flux de base que nous verrons ensuite, il semble qu'il ne puisse pas être géré sans modification.
Dans chaque bibliothèque, vérifiez où le flux de base passé est lu.
Le flux en .NET est appelé flux de base pour le distinguer du flux au sens de bzip2.
SharpZipLib: src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs
417: thech = baseStream.ReadByte();
231: int magic0 = bsStream.ReadByte();
232: int magic1 = bsStream.ReadByte();
233: int magic2 = bsStream.ReadByte();
242: int magic3 = bsStream.ReadByte();
378: thech = (char)bsStream.ReadByte();
649: thech = (char)bsStream.ReadByte();
717: thech = (char)bsStream.ReadByte();
814: thech = (char)bsStream.ReadByte();
9: private const int BufferSize = 128 * 1024;
14: private readonly byte[] _buffer = new byte[BufferSize];
368: _data.avail_in = _stream.Read(_buffer, 0, ufferSize);
SharpZipLib et SharpCompress lisent un octet à la fois avec ReadByte
si nécessaire. Par conséquent, il ne semble pas dépasser même s'il casse au niveau du délimiteur de flux bzip2. Puisque le nom de variable «thech» (le caractère?) Est commun, il peut y avoir quelque chose en commun. (Je ne vois pas ce nom de variable dans libbz2)
AR.Compression.BZip2 lit dans le tampon avec une longueur fixe. Puisqu'il n'est pas développé par lui-même, il peut ne pas être possible de le lire en unités d'octets. Même si le traitement est séparé pour chaque flux bzip2, il dépassera, donc certaines mesures sont nécessaires.
La lecture octet par octet est précise par rapport à la position du flux de base, mais elle est désavantageuse en termes de vitesse de traitement.
Comparez le temps nécessaire pour décompresser un gros fichier.
Wikipedia Utilisez la version japonaise des données de vidage. Ce fichier a une configuration multi-flux.
Développez séquentiellement le flux avec SharpZipLib et SharpCompress.
test1.fsx
#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System
open System.IO
open ICSharpCode.SharpZipLib.BZip2
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
use fs = new FileStream(target, FileMode.Open)
let buffer = Array.zeroCreate<byte>(1024 * 1024)
let mutable streams, bytes = 0, 0L
while fs.Position < fs.Length do
use bz = new BZip2InputStream(fs, IsStreamOwner = false)
let mutable len = 1
while len > 0 do
len <- bz.Read(buffer, 0, buffer.Length)
bytes <- bytes + int64 len
streams <- streams + 1
Console.WriteLine("streams: {0:#,0}, bytes: {1:#,0}", streams, bytes)
test2.fsx
#r "SharpCompress.dll"
open System
open System.IO
open SharpCompress.Compressors
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
let mutable ignore = true
use fs = { new FileStream(target, FileMode.Open) with
override __.Dispose disposing = if not ignore then base.Dispose disposing }
let buffer = Array.zeroCreate<byte>(1024 * 1024)
let mutable streams, bytes = 0, 0L
while fs.Position < fs.Length do
use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, false)
let mutable len = 1
while len > 0 do
len <- bz.Read(buffer, 0, buffer.Length)
bytes <- bytes + int64 len
streams <- streams + 1
ignore <- false
Console.WriteLine("streams: {0:#,0}, bytes: {1:#,0}", streams, bytes)
Résultat d'exécution
$ time ./test1.exe # SharpZipLib
streams: 24,957, bytes: 13,023,068,290
real 16m2.849s
$ time ./test2.exe # SharpCompress
streams: 24,957, bytes: 13,023,068,290
real 18m26.520s
SharpZipLib semble être plus rapide.
Extrayez tous les flux à la fois avec SharpCompress et AR.Compression.BZip2.
test3.fsx
#r "SharpCompress.dll"
open System
open System.IO
open SharpCompress.Compressors
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
use fs = new FileStream(target, FileMode.Open)
use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, true)
let buffer = Array.zeroCreate<byte>(1024 * 1024)
let mutable bytes, len = 0L, 1
while len > 0 do
len <- bz.Read(buffer, 0, buffer.Length)
bytes <- bytes + int64 len
Console.WriteLine("bytes: {0:#,0}", bytes)
test4.fsx
#r "AR.Compression.BZip2.dll"
open System
open System.IO
open System.IO.Compression
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
use fs = new FileStream(target, FileMode.Open)
use bz = new BZip2Stream(fs, CompressionMode.Decompress, false)
let buffer = Array.zeroCreate<byte>(1024 * 1024)
let mutable bytes, len = 0L, 1
while len > 0 do
len <- bz.Read(buffer, 0, buffer.Length)
bytes <- bytes + int64 len
Console.WriteLine("bytes: {0:#,0}", bytes)
Résultat d'exécution
$ time ./test3.exe # SharpCompress
bytes: 13,023,068,290
real 17m36.925s
$ time ./test4.exe # AR.Compression.BZip2
bytes: 13,023,068,290
real 8m23.916s
AR.Compression.BZip2 est rapide car il appelle la bibliothèque native.
Python
Comparez avec le module bz2 de Python. C'est également un wrapper natif.
[Référence] Développer séquentiellement bzip2 multi-flux avec Python
test5.py (séquentiel)
import bz2
target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
streams = 0
bytes = 0
size = 1024 * 1024 # 1MB
with open(target, "rb") as f:
decompressor = bz2.BZ2Decompressor()
data = b''
while data or (data := f.read(size)):
bytes += len(decompressor.decompress(data))
data = decompressor.unused_data
if decompressor.eof:
decompressor = bz2.BZ2Decompressor()
streams += 1
print(f"streams: {streams:,}, bytes: {bytes:,}")
test6.py (collectif)
import bz2
target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
bytes = 0
size = 1024 * 1024 # 1MB
with bz2.open(target, "rb") as f:
while (data := f.read(size)):
bytes += len(data)
print(f"bytes: {bytes:,}")
Résultat d'exécution
$ time py.exe test5.py #Séquentiel
streams: 24,957, bytes: 13,023,068,290
real 8m12.155s
$ time py.exe test6.py #Masse
bytes: 13,023,068,290
real 8m1.476s
La plupart du traitement est effectué par libbz2, et le reste du traitement est lent et rapide.
bzcat
Il mesure également la commande WSL1 bzcat.
$ time bzcat jawiki-20200501-pages-articles-multistream.xml.bz2 > /dev/null
real 8m21.056s
user 8m5.563s
sys 0m15.422s
Résumez les résultats. Ajoutez le résultat de la mesure de WSL1 (Mono). La vitesse de Python est accrocheuse.
Séquentiel(Win) | Séquentiel(WSL1) | Masse(Win) | Masse(WSL1) | |
---|---|---|---|---|
SharpZipLib | 16m02.849s | 22m49.375s | ||
SharpCompress | 18m26.520s | 23m56.694s | 17m36.925s | 22m54.247s |
AR.Compression.BZip2 | 8m23.916s | 8m36.495s | ||
Python (bz2) | 8m12.155s | 8m45.590s | 8m01.476s | 8m28.749s |
bzcat | 8m21.056s |
Les bibliothèques implémentées en code managé ont pris plus du double du temps. Si vous n'avez pas d'obligation gérée, il est plus sûr d'utiliser AR.Compression.BZip2.
DeflateStream utilisé par System.IO.Compression.GZipStream semble être passé de sa propre implémentation à un wrapper natif.
À partir de .NET Framework 4.5, la classe DeflateStream utilise la bibliothèque zlib pour la compression.
Consultez les articles suivants pour les vidages Wikipédia.
Cet article traite de bzip2 dans SharpZipLib.
Un article qui mentionne SharpCompress.