C#でGZipStreamを試す。

C# コンピュータ
C#

GZipStreamの圧縮展開の速度、ファイルサイズなどを確認してみます。

GZIPといいますとlinuxなどでよく使われていたファイルの圧縮方式で、複数のファイルをアーカイブする機能は無いので、まずアーカイブに特化したtarファイルを作成し、そのファイルをGZIPで圧縮して.tar.gz形式のファイルとして扱った記憶があります。

また、HTTPで転送する場合の圧縮形式としても使われていたと思います。連続するデータを順次圧縮展開するような使われ方をするためか、今回のサンプルプログラムでは書き込み(圧縮)は一気に行うことが出来ましたが、読み込み(展開)はバッファを用意し少しづつ処理する必要がありました。当初は読み込みも一気に読み込むようにプログラミングしたのですが、データが一部しか読み込まれずかなり悩みました。

テストの内容

テスト用の画像ファイル(PNG形式 3840×2160 32bitRGBA)からBitmapImage生成
BitmapImageのPixcelをbyte配列に変換
byte配列をgzipで圧縮ファイルに保存
gzipで圧縮されたファイルを展開しBitmapImage生成
BitmapImageをPNG形式で保存

テスト結果

圧縮レベルRead

ToBin

Comp

DeComp

ToBmp

SaveSize
Optimal246ms17ms485ms70ms81ms539ms2742944Byte
Fastest254ms19ms188ms84ms84ms559ms3423294Byte
NoCompression264ms20ms98ms52ms83ms563ms33182683Byte
SmallestSize262ms15ms795ms66ms81ms533ms2624433Byte

ReadはPNGファイルの読み込みで、DeComp+ToBmpがgzファイルの読み込み時間になります。
最速は無圧縮のNoCompressionですが、どの圧縮レベルでもPNGファイルの読み込みよりは速い結果になりました。

SaveはPNGファイルの書き出しで、Comp+ToBinがgzファイルへの書き出し時間になります。
こちらは無圧縮のNoCompressionを除くとFastestが最速で、PNGファイルの保存より遅いのは圧縮率の高いSmallestSizeのみでした。

Sizeはgzファイルのサイズで、PNGファイルのサイズは3411021Byteでした。
無圧縮のNoCompressionのサイズは吐出して大きいですが、それ以外はPNGのサイズと同じか少し小さいぐらいになります。

圧縮する素材によって結果は大きく異なるので、PNGとの比較は余り意味は無いですが、圧縮レベルで速度やファイルサイズが異なる点は結構面白いと思います。

ソースコード

using System.Diagnostics;
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO;

class Program
{
    static void Test1()
    {
        const string imgFile = @".\sample.png";
        const string gzFile = @".\sample.gz";

        Stopwatch sw = new();
        sw.Start();
        sw.Stop();

        sw.Restart();
        BitmapImage bmp = new();
        using (FileStream fs = new (imgFile, FileMode.Open, FileAccess.Read))
        {
            bmp.BeginInit();
            bmp.CacheOption = BitmapCacheOption.OnLoad;
            bmp.StreamSource = fs;
            bmp.EndInit();
        }
        bmp.Freeze();
        sw.Stop();
        Console.WriteLine($"ReadPng:{sw.ElapsedMilliseconds}ms");

        sw.Restart();
        int stride = bmp.PixelWidth * 4;
        byte[] pixels = new byte[stride * bmp.PixelHeight];
        bmp.CopyPixels(new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight), pixels, stride, 0);
        sw.Stop();
        Console.WriteLine($"BmpToBin:{sw.ElapsedMilliseconds}ms");

        sw.Restart();
        using (FileStream fs1 = new (gzFile, FileMode.Create, FileAccess.Write))
        {
            System.IO.Compression.GZipStream gs =
                new (fs1, System.IO.Compression.CompressionLevel.Optimal);
            //System.IO.Compression.GZipStream gs =
            //    new (fs1, System.IO.Compression.CompressionLevel.Fastest);
            //System.IO.Compression.GZipStream gs =
            //    new (fs1, System.IO.Compression.CompressionLevel.NoCompression);
            //System.IO.Compression.GZipStream gs =
            //    new (fs1, System.IO.Compression.CompressionLevel.SmallestSize);
            gs.Write(pixels, 0, pixels.Length);

            sw.Stop();
            Console.WriteLine($"GzipCompress:{sw.ElapsedMilliseconds}ms {pixels.Length} => {fs1.Length}");
            gs.Close();

        }
    }
    static void Test2()
    {
        const string imgFile = @".\sample.png";
        const string gzFile = @".\sample.gz";
        const string saveFile = @".\sample2.png";

        Stopwatch sw = new();
        sw.Start();
        sw.Stop();

        sw.Restart();
        BitmapImage bmp = new();
        using (FileStream fs = new (imgFile, FileMode.Open, FileAccess.Read))
        {
            bmp.BeginInit();
            bmp.CacheOption = BitmapCacheOption.OnLoad;
            bmp.StreamSource = fs;
            bmp.EndInit();
        }
        bmp.Freeze();
        sw.Stop();
        Console.WriteLine($"ReadPng:{sw.ElapsedMilliseconds}ms");

        int stride = bmp.PixelWidth * 4;
        byte[] pixels = new byte[stride * bmp.PixelHeight];

        sw.Restart();

        using (FileStream fs2 = new (gzFile, FileMode.Open, FileAccess.Read))
        {
            System.IO.Compression.GZipStream gs2 =
                new (fs2, System.IO.Compression.CompressionMode.Decompress);
            int totalRead = 0;
            while (totalRead < pixels.Length)
            {
                int bytesRead = gs2.Read(pixels.AsSpan(totalRead));
                if (bytesRead == 0) break;
                totalRead += bytesRead;
            }
            sw.Stop();
            Console.WriteLine($"GzipDeCompress:{sw.ElapsedMilliseconds}ms {fs2.Length} => {pixels.Length}");
            gs2.Close();
        }
        sw.Restart();
        WriteableBitmap wb = new WriteableBitmap(
            bmp.PixelWidth,
            bmp.PixelHeight,
            bmp.DpiX,
            bmp.DpiY,
            bmp.Format,
            null);
        wb.WritePixels(
            new Int32Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight),
            pixels,
            stride,
            0);
        wb.Freeze();
        sw.Stop();
        Console.WriteLine($"BinToBmp:{sw.ElapsedMilliseconds}ms");

        sw.Restart();
        using (FileStream fs2 = new (saveFile, FileMode.Create, FileAccess.Write))
        {
            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(wb));
            encoder.Save(fs2);
        }
        sw.Stop();
        Console.WriteLine($"SavePng:{sw.ElapsedMilliseconds}ms");
    }
    static void Main()
    {
        Test1();
        Test2();
    }
}

コメント