Untersuchen Sie die bzip2-Bibliothek von .NET Framework

Informieren Sie sich über Bibliotheken, die bzip2 in .NET Framework unterstützen. Vergleichen Sie die Verarbeitungsgeschwindigkeit mit Python und bzcat.

Dieser Artikel hat eine Python-Version.

Bibliothek

Ich habe drei verschiedene Bibliotheken gefunden, die bzip2 unterstützen.

  1. SharpZipLib: Neu implementiert mit verwaltetem Code
  2. SharpCompress: Neu implementiert mit verwaltetem Code
  3. AR.Compression.BZip2 Native Library Wrapper

Überprüfen Sie, wie lange diese Bibliotheken Multistreams verarbeiten und bereitstellen.

Multistream

Daten, die einzeln mit bzip2 komprimiert und verkettet werden, werden als Multi-Stream bezeichnet.

Erstellungsbeispiel


$ echo -n hello | bzip2 > a.bz2
$ echo -n world | bzip2 > b.bz2
$ cat a.bz2 b.bz2 > ab.bz2

Es kann so behandelt werden, wie es ist, mit Befehlen wie "bzcat".

$ bzcat ab.bz2
helloworld

Es wird für die parallele Komprimierung pbzip2 und für Wikipedia-Dumps verwendet.

[Addition] Dasselbe kann mit gzip gemacht werden, was im folgenden Artikel untersucht wird.

SharpZipLib

Unterstützt mehrere Komprimierungsalgorithmen.

Die bzip2-Implementierung ist unten.

Minimale Konfiguration

Wir haben bestätigt, dass bzip2 nur mit den folgenden 6 Dateien extrahiert werden kann.

Prüfung

Verwenden Sie die von NuGet erhaltene Bibliothek anstelle der Mindestkonfiguration.

nuget install SharpZipLib
cp SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll .

Versuchen Sie, ab.bz2 zu erweitern, das als Multi-Stream-Beispiel erstellt wurde.

#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

Es wird nur der erste Stream verarbeitet.

Wenn Sie sich BZip2InputStream.cs ansehen, scheint es kein Multi-Stream zu unterstützen, daher müssen Sie es nacheinander lesen.

#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

Unterstützt mehrere Komprimierungsalgorithmen.

Die bzip2-Implementierung ist unten.

Dieses Verzeichnis kann extrahiert und unabhängig verwendet werden. Das einzige ist, dass es keine eigene Definition von "CompressionMode" gibt, aber den vorhandenen Aufzählungstyp ersetzt.

BZip2Stream.cs (zusätzlich)


using System.IO.Compression;

Minimale Konfiguration

Wir haben bestätigt, dass bzip2 nur mit den folgenden drei Dateien extrahiert werden kann.

Die Klasse CBZip2InputStream ist jedoch "intern" und muss daher "öffentlich" sein.

Prüfung

Verwenden Sie die von NuGet erhaltene Bibliothek anstelle der Mindestkonfiguration.

nuget install sharpcompress
cp SharpCompress.0.25.1/lib/net46/SharpCompress.dll .

Versuchen Sie, ab.bz2 zu erweitern, das als Multi-Stream-Beispiel erstellt wurde.

#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())

Ausführungsergebnis


helloworld

Mit Unterstützung für Multi-Stream konnte ich alles auf einmal lesen.

Wenn Sie das Multistream-Flag auf "false" setzen, wird "fs" geschlossen, wenn das Ende eines Streams erreicht ist. Wenn man sich CBZip2InputStream.cs ansieht, scheint es nicht offen zu bleiben. Daher scheint es, dass die einzige Möglichkeit, es nacheinander zu lesen, darin besteht, etwas zu tun, das "Entsorgen" ignoriert.

#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

Ausführungsergebnis


hello
world

AR.Compression.BZip2

Im Gegensatz zu anderen Bibliotheken ist es auf bzip2 spezialisiert. Dekomprimierung und Komprimierung werden in einer Klasse implementiert, sodass wir die Mindestkonfiguration nicht separat betrachten.

Es ist in NuGet registriert.

Bauen

Das Teilen der Bibliothek mit Mono erfordert ein wenig Arbeit, daher werde ich sie dieses Mal selbst erstellen, anstatt NuGet zu verwenden.

Benennen Sie die in P / Invoke angegebene DLL um.

10:        private const string DllName = "libbz2";

Dies verwendet libbz2.dll unter Windows und libbz2.so unter WSL. Die WSL verweist auf /usr/lib/libbz2.so, auch wenn sie sich nicht im aktuellen Verzeichnis befindet.

Erstellen Sie die DLL.

csc -o -out:AR.Compression.BZip2.dll -t:library -unsafe sources/AR.BZip2/*.cs

Bzip2 für Windows verwendet die unten verteilten Binärdateien.

Prüfung

Versuchen Sie, ab.bz2 zu erweitern, das als Multi-Stream-Beispiel erstellt wurde.

#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())

Ausführungsergebnis


helloworld

Alle Streams wurden gleichzeitig erweitert. Wenn man sich BZip2Stream.cs ansieht, scheint es, dass es nicht sequentiell gelesen werden soll. In Anbetracht der Behandlung des Basisstroms, die als nächstes zu sehen sein wird, scheint es, dass er nicht ohne Änderung behandelt werden kann.

Basisstrom laden

Überprüfen Sie in jeder Bibliothek, wo der übergebene Basisstrom gelesen wird.

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 und SharpCompress lesen bei Bedarf jeweils ein Byte mit "ReadByte". Daher scheint es nicht zu überlaufen, selbst wenn es am bzip2-Stream-Trennzeichen unterbrochen wird. Da der Variablenname "thech" (das Zeichen?) Häufig ist, kann etwas gemeinsam sein. (Ich sehe diesen Variablennamen nicht in libbz2)

AR.Compression.BZip2 liest mit fester Länge in den Puffer. Da es nicht von selbst erweitert wird, ist es möglicherweise nicht möglich, es in Byteeinheiten zu lesen. Selbst wenn die Verarbeitung für jeden bzip2-Stream getrennt ist, wird sie überlaufen, sodass einige Maßnahmen erforderlich sind.

Das byteweise Lesen ist in Bezug auf die Position des Basisstroms genau, jedoch in Bezug auf die Verarbeitungsgeschwindigkeit nachteilig.

Messung

Vergleichen Sie die Zeit, die zum Entpacken einer großen Datei benötigt wird.

Verwenden Sie die japanische Version der Wikipedia-Dump-Daten. Diese Datei hat eine Multi-Stream-Konfiguration.

Sequentielle Erweiterung

Erweitern Sie den Stream nacheinander mit SharpZipLib und 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)

Ausführungsergebnis


$ 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 scheint schneller zu sein.

Massenbereitstellung

Extrahieren Sie alle Streams gleichzeitig mit SharpCompress und 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)

Ausführungsergebnis


$ 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 ist schnell, da es die native Bibliothek aufruft.

Python

Vergleiche mit Pythons bz2-Modul. Dies ist auch ein nativer Wrapper.

[Referenz] Multi-Stream bzip2 nacheinander mit Python erweitern

test5.py (sequentiell)


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 (kollektiv)


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:,}")

Ausführungsergebnis


$ time py.exe test5.py  #Sequentiell
streams: 24,957, bytes: 13,023,068,290

real    8m12.155s

$ time py.exe test6.py  #Bulk
bytes: 13,023,068,290

real    8m1.476s

Der größte Teil der Verarbeitung wird von libbz2 ausgeführt, und der Rest der Verarbeitung ist gering und schnell.

bzcat

Es misst auch den Befehl WSL1 bzcat.

$ time bzcat jawiki-20200501-pages-articles-multistream.xml.bz2 > /dev/null

real    8m21.056s
user    8m5.563s
sys     0m15.422s

Zusammenfassung

Fassen Sie die Ergebnisse zusammen. Fügen Sie das Messergebnis von WSL1 (Mono) hinzu. Die Geschwindigkeit von Python ist ein Blickfang.

Sequentiell(Win) Sequentiell(WSL1) Bulk(Win) Bulk(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

In verwaltetem Code implementierte Bibliotheken dauerten mehr als doppelt so lange. Wenn Sie die Bindung nicht verwaltet haben, ist es sicherer, AR.Compression.BZip2 zu verwenden.

Ab .NET Framework 4.5 verwendet die DeflateStream-Klasse die zlib-Bibliothek zur Komprimierung.

In Verbindung stehender Artikel

In den folgenden Artikeln finden Sie Wikipedia-Dumps.

Referenz

Dieser Artikel befasst sich mit bzip2 in SharpZipLib.

Ein Artikel, in dem SharpCompress erwähnt wird.

Recommended Posts

Untersuchen Sie die bzip2-Bibliothek von .NET Framework
Das Common Clk Framework
Ich habe die Changefinder-Bibliothek ausprobiert!