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.