Comparing compression options in .NET

January 09, 2023#Software Development
Article
Author image.

Philippe Vaillancourt, Senior Consultant

Compression can be a useful tool for reducing the size of data transmitted over a network or stored on disk. The .NET framework includes several classes that can be used to compress and decompress data, including the BrotliStream, GZipStream, DeflateStream, and ZLibStream classes. In this article, we will look at these classes and benchmark their performance when compressing and decompressing data. We will use both the Optimal and SmallestSize compression levels. Our test data for this benchmark will be a byte array containing the entire bible (3.9 megabytes).

Example Compression Benchmark Method

Here is an example of a benchmark method that measures the time and memory required to compress a byte array using the BrotliStream class:

[Benchmark]
public byte[] CompressBrotli()
{
    var compression = Compression == "Optimal" ? CompressionLevel.Optimal : CompressionLevel.SmallestSize;
    using var output = new MemoryStream();
    using var compressor = new BrotliStream(output, compression, true);    
    compressor.Write(Data, 0, Data.Length);
    compressor.Close();
    return output.ToArray();    
}

This method creates a MemoryStream called output to store the compressed data, and a BrotliStream called compressor that wraps the output stream and performs the compression. The Write method is used to write the contents of the Data array to the compressor stream, and the Close method is called to tell the stream that no more data will be written. Finally, the ToArray method of the output stream is used to return the compressed data as a byte array.

Benchmark Results for Compression

The following table shows the results of running the compression benchmark on an input data set with a size of 3952 kb:


BenchmarkDotNet=v0.12.1, OS=Windows 10.0.22621
Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET Core SDK=7.0.101
  [Host]     : .NET Core 7.0.1 (CoreCLR 7.0.122.56804, CoreFX 7.0.122.56804), X64 RyuJIT
  DefaultJob : .NET Core 7.0.1 (CoreCLR 7.0.122.56804, CoreFX 7.0.122.56804), X64 RyuJIT
Method Compression Mean Error StdDev Median Gen 0 Gen 1 Gen 2 Allocated
CompressBrotli Optimal 89.09 ms 3.404 ms 9.488 ms 85.06 ms 250.0000 250.0000 250.0000 4.99 MB
CompressGZip Optimal 138.35 ms 1.296 ms 1.149 ms 137.87 ms 250.0000 250.0000 250.0000 5.14 MB
CompressDeflate Optimal 138.54 ms 1.339 ms 1.253 ms 138.46 ms 250.0000 250.0000 250.0000 5.14 MB
CompressZLib Optimal 139.66 ms 0.905 ms 0.803 ms 139.29 ms 250.0000 250.0000 250.0000 5.14 MB
CompressBrotli SmallestSize 7,709.19 ms 28.109 ms 24.918 ms 7,709.24 ms - - - 2.79 MB
CompressGZip SmallestSize 374.81 ms 5.081 ms 4.243 ms 374.56 ms - - - 5.12 MB
CompressDeflate SmallestSize 376.52 ms 3.419 ms 2.855 ms 375.94 ms - - - 5.12 MB
CompressZLib SmallestSize 377.37 ms 5.146 ms 4.813 ms 374.87 ms - - - 5.12 MB

In terms of the compression achieved, these were the results:

Method Compression Size Reduction
CompressBrotli Optimal 1072 kb 27.13 %
CompressGZip Optimal 1171 kb 29.63 %
CompressDeflate Optimal 1171 kb 29.63 %
CompressZLib Optimal 1171 kb 29.63 %
CompressBrotli SmallestSize 868 kb 21.96 %
CompressGZip SmallestSize 1149 kb 29.07 %
CompressDeflate SmallestSize 1149 kb 29.07 %
CompressZLib SmallestSize 1149 kb 29.07 %

The results of the compression benchmark show that the BrotliStream class was the fastest at compressing the data in the optimal mode. In this mode, the BrotliStream class was able to compress the data in 89.09 ms, while the GZipStream, DeflateStream, and ZLibStream classes took 138.35 ms, 138.54 ms, and 139.66 ms, respectively. The BrotliStream class also used the least amount of memory, with a maximum allocation of 4.99 MB.

However, when it comes to compressing the data in the smallest size mode, the BrotliStream class was significantly slower than the other classes, taking 7,709.19 ms compared to 374.81 ms for GZipStream, 376.52 ms for DeflateStream, and 377.37 ms for ZLibStream.

When it comes to the compression achieved, the BrotliStream class was able to compress the data in the optimal mode by 27.13 %, while the GZipStream, DeflateStream, and ZLibStream classes were able to compress the data by 29.63 %. In the smallest size mode, the BrotliStream class was able to compress the data by 21.96 %, while the GZipStream, DeflateStream, and ZLibStream classes were able to compress the data by 29.07 %.

Example Decompression Benchmark Method

Here is an example of a benchmark method that measures the time and memory required to decompress a byte array using the BrotliStream class:

[Benchmark]
public byte[] DecompressBrotli()
{
    using var input = new MemoryStream(Data);
    using var decompressor = new BrotliStream(input, CompressionMode.Decompress);
    using var output = new MemoryStream();
    decompressor.CopyTo(output);
    return output.ToArray();
}

This method creates a MemoryStream called input that wraps the Data array, and another MemoryStream called output to store the decompressed data. It also creates a BrotliStream called decompressor that wraps the input stream and performs the decompression. The CopyTo method is used to copy the contents of the decompressor stream to the output stream, and the ToArray method of the output stream is used to return the decompressed data as a byte array.

Benchmark Results for Decompression

The following table shows the results of running the decompression benchmark on the data compressed in the previous benchmark:

Method Compression Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
DecompressBrotli Optimal 12.73 ms 0.252 ms 0.385 ms 859.3750 859.3750 859.3750 11.74 MB
DecompressGZip Optimal 13.11 ms 0.138 ms 0.129 ms 156.2500 156.2500 156.2500 11.74 MB
DecompressDeflate Optimal 12.81 ms 0.116 ms 0.103 ms 156.2500 156.2500 156.2500 11.74 MB
DecompressZLib Optimal 13.99 ms 0.085 ms 0.071 ms 156.2500 156.2500 156.2500 11.74 MB
DecompressBrotli SmallestSize 12.57 ms 0.250 ms 0.233 ms 171.8750 171.8750 171.8750 11.74 MB
DecompressGZip SmallestSize 12.94 ms 0.117 ms 0.110 ms 156.2500 156.2500 156.2500 11.74 MB
DecompressDeflate SmallestSize 12.81 ms 0.127 ms 0.119 ms 156.2500 156.2500 156.2500 11.74 MB
DecompressZLib SmallestSize 14.03 ms 0.110 ms 0.097 ms 156.2500 156.2500 156.2500 11.74 MB

In these results, we can see that the BrotliStream class was the fastest at decompressing the data, taking an average of 12.73 ms to complete the task. The GZipStream, DeflateStream, and ZLibStream classes all had similar performance, taking an average of 13.11 ms, 12.81 ms, and 13.99 ms, respectively. The results were not materially different when decompressing the data in the smallest size mode.

Conclusion

Looking at the results, in terms of performance, there really isn’t any reason to use anything but Brotli. If we’re looking for the best bang for our buck, Brotli offers the best compression ratio and the fastest compression and decompression times in the Optimal compression level.

If we’re looking for the absolute smallest size, Brotli is still the best option in terms of the compression ratio, but it is significantly slower than the other compression algorithms. But here’s the thing, Brotli on Optimal is still both faster and offers better compression than the other compression algorithms are on SmallestSize. In other words, Brotli is the best option for compression in almost every scenario.

A word of caution, the results you might achieve will differ based on the data you are trying to compress so it might be worth testing the different compression algorithms to see which one works best for your data.

Source Code

https://github.com/snowfrogdev/CompressionBenchmarks


Copyright © 2023 NimblePros - All Rights Reserved