Examinez la bibliothèque bzip2 du .NET Framework

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.

Bibliothèque

J'ai trouvé trois bibliothèques différentes prenant en charge bzip2.

  1. SharpZipLib: réimplémenté avec du code géré
  2. SharpCompress: réimplémenté avec du code géré
  3. AR.Compression.BZip2 Encapsuleur de bibliothèque natif

Vérifiez combien de temps ces bibliothèques gèrent et déploient des flux multiples.

Multi-flux

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.

Configuration minimale

Nous avons confirmé que bzip2 peut être extrait avec seulement les 6 fichiers suivants.

tester

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.

#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;

Configuration minimale

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`.

tester

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.

#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.

Construire

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.

tester

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.

Charger le flux de base

Dans chaque bibliothèque, vérifiez où le flux de base passé est lu.

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.

la mesure

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.

Expansion séquentielle

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.

Déploiement en masse

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ésumé

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.

À partir de .NET Framework 4.5, la classe DeflateStream utilise la bibliothèque zlib pour la compression.

Article associé

Consultez les articles suivants pour les vidages Wikipédia.

référence

Cet article traite de bzip2 dans SharpZipLib.

Un article qui mentionne SharpCompress.

Recommended Posts

Examinez la bibliothèque bzip2 du .NET Framework
Le cadre Common Clk
J'ai essayé la bibliothèque changefinder!