diff --git a/benchmarks/Compression/Compress.cs b/benchmarks/Compression/Compress.cs index 66b7b56ee..994de3620 100644 --- a/benchmarks/Compression/Compress.cs +++ b/benchmarks/Compression/Compress.cs @@ -46,6 +46,13 @@ public void IronSnappy() _ = compressor.Compress(Data); } + [Benchmark] + public void BuiltinZlib() + { + using var compressor = Factories.BuiltinZlibCompressionCompressorFactory.Create(); + _ = compressor.Compress(Data); + } + [Benchmark] public void DotNetZip() { diff --git a/benchmarks/Compression/Decompress.cs b/benchmarks/Compression/Decompress.cs index 902511a09..298070597 100644 --- a/benchmarks/Compression/Decompress.cs +++ b/benchmarks/Compression/Decompress.cs @@ -37,6 +37,8 @@ public void Setup() K4aosCompressionLz4Data = compressor.Compress(data); using (var compressor = Factories.IronSnappyCompressorFactory.Create()) IronSnappyData = compressor.Compress(data); + using (var compressor = Factories.BuiltinZlibCompressionCompressorFactory.Create()) + BuiltinZlibData = compressor.Compress(data); using (var compressor = Factories.DotNetZipCompressorFactory.Create()) DotNetZipData = compressor.Compress(data); using (var compressor = Factories.ZstdNetCompressorFactory.Create()) @@ -48,6 +50,7 @@ public void Setup() public int DecompressedSize { get; private set; } public ReadOnlySequence K4aosCompressionLz4Data { get; private set; } public ReadOnlySequence IronSnappyData { get; private set; } + public ReadOnlySequence BuiltinZlibData { get; private set; } public ReadOnlySequence DotNetZipData { get; private set; } public ReadOnlySequence ZstdNetData { get; private set; } public ReadOnlySequence ZstdSharpData { get; private set; } @@ -66,6 +69,13 @@ public void IronSnappy() _ = decompressor.Decompress(IronSnappyData, DecompressedSize); } + [Benchmark] + public void BuiltinZlib() + { + using var decompressor = Factories.BuiltinZlibCompressionDecompressorFactory.Create(); + _ = decompressor.Decompress(BuiltinZlibData, DecompressedSize); + } + [Benchmark] public void DotNetZip() { diff --git a/benchmarks/Compression/Factories.cs b/benchmarks/Compression/Factories.cs index 045e37791..7de2a2fb1 100644 --- a/benchmarks/Compression/Factories.cs +++ b/benchmarks/Compression/Factories.cs @@ -31,6 +31,11 @@ static Factories() IronSnappyCompressorFactory = compressor!; IronSnappyDecompressorFactory = decompressor!; + if (!BuiltinZlibCompression.TryLoading(out compressor, out decompressor)) + throw new Exception("Could not load BuiltinZlibCompression"); + BuiltinZlibCompressionCompressorFactory = compressor!; + BuiltinZlibCompressionDecompressorFactory = decompressor!; + if (!ZlibCompression.TryLoading(out compressor, out decompressor)) throw new Exception("Could not load DotNetZip"); DotNetZipCompressorFactory = compressor!; @@ -53,6 +58,9 @@ static Factories() public static ICompressorFactory IronSnappyCompressorFactory { get; } public static IDecompressorFactory IronSnappyDecompressorFactory { get; } + public static ICompressorFactory BuiltinZlibCompressionCompressorFactory { get; } + public static IDecompressorFactory BuiltinZlibCompressionDecompressorFactory { get; } + public static ICompressorFactory DotNetZipCompressorFactory { get; } public static IDecompressorFactory DotNetZipDecompressorFactory { get; } diff --git a/benchmarks/Compression/Program.cs b/benchmarks/Compression/Program.cs index 1b9142608..6f0aa4e7f 100644 --- a/benchmarks/Compression/Program.cs +++ b/benchmarks/Compression/Program.cs @@ -33,6 +33,8 @@ static void OutputCompressionInfo(MessageSize size, MessageType type) Console.WriteLine($"\tCompressed with IronSnappy: {compressor.Compress(data).Length}"); using (var compressor = Factories.DotNetZipCompressorFactory.Create()) Console.WriteLine($"\tCompressed with DotNetZip: {compressor.Compress(data).Length}"); + using (var compressor = Factories.BuiltinZlibCompressionCompressorFactory.Create()) + Console.WriteLine($"\tCompressed with System.IO.Compression: {compressor.Compress(data).Length}"); using (var compressor = Factories.ZstdNetCompressorFactory.Create()) Console.WriteLine($"\tCompressed with ZstdNet: {compressor.Compress(data).Length}"); using (var compressor = Factories.ZstdSharpCompressorFactory.Create()) diff --git a/src/DotPulsar/Internal/Compression/BuiltinZlibCompression.cs b/src/DotPulsar/Internal/Compression/BuiltinZlibCompression.cs new file mode 100644 index 000000000..d1b3cc643 --- /dev/null +++ b/src/DotPulsar/Internal/Compression/BuiltinZlibCompression.cs @@ -0,0 +1,77 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace DotPulsar.Internal.Compression; + +using DotPulsar.Internal.Abstractions; +using System.Buffers; +using System.Reflection; + +public static class BuiltinZlibCompression +{ + public static bool TryLoading(out ICompressorFactory? compressorFactory, out IDecompressorFactory? decompressorFactory) + { + try + { + var assembly = Assembly.Load("System.IO.Compression"); + var types = assembly.GetTypes(); + + // Find the ZLibStream + var zlibStreamType = types.FirstOrDefault(t => t.FullName == "System.IO.Compression.ZLibStream"); + + // Find the enum values for the ZLibStream constructors + var compressionLevelType = types.FirstOrDefault(t => t.FullName == "System.IO.Compression.CompressionLevel"); + var compressionLevelOptimal = compressionLevelType?.GetEnumValues().GetValue(0); + var compressionModeType = types.FirstOrDefault(t => t.FullName == "System.IO.Compression.CompressionMode"); + var compressionModeDecompress = compressionModeType?.GetEnumValues().GetValue(0); + + compressorFactory = new CompressorFactory(PulsarApi.CompressionType.Zlib, () => new Compressor(CreateCompressor(zlibStreamType, compressionLevelOptimal))); + decompressorFactory = new DecompressorFactory(PulsarApi.CompressionType.Zlib, () => new Decompressor(CreateDecompressor(zlibStreamType, compressionModeDecompress))); + + return CompressionTester.TestCompression(compressorFactory, decompressorFactory); + } + catch + { + // Ignore + } + + compressorFactory = null; + decompressorFactory = null; + + return false; + } + + private static Func, ReadOnlySequence> CreateCompressor(Type? zlibStreamType, object? compressionLevelOptimal) + => data => + { + using var dataStream = new MemoryStream(data.ToArray()); + using var compressedStream = new MemoryStream(); + using var compressor = (Stream) Activator.CreateInstance(zlibStreamType!, new object[] { compressedStream, compressionLevelOptimal! })!; + dataStream.CopyTo(compressor); + compressor.Close(); + return new ReadOnlySequence(compressedStream.ToArray()); + }; + + private static Func, int, ReadOnlySequence> CreateDecompressor(Type? zlibStreamType, object? compressionModeDecompress) + => (data, _) => + { + using var dataStream = new MemoryStream(data.ToArray()); + using var decompressedStream = new MemoryStream(); + using var decompressor = (Stream) Activator.CreateInstance(zlibStreamType!, new object[] { dataStream, compressionModeDecompress! })!; + decompressor.CopyTo(decompressedStream); + decompressor.Close(); + decompressedStream.Position = 0; + return new ReadOnlySequence(decompressedStream.ToArray()); + }; +} diff --git a/src/DotPulsar/Internal/Compression/CompressionFactories.cs b/src/DotPulsar/Internal/Compression/CompressionFactories.cs index 533fb1b21..288d168aa 100644 --- a/src/DotPulsar/Internal/Compression/CompressionFactories.cs +++ b/src/DotPulsar/Internal/Compression/CompressionFactories.cs @@ -46,7 +46,9 @@ private static void LoadSupportForSnappy() private static void LoadSupportForZlib() { - if (ZlibCompression.TryLoading(out var compressorFactory, out var decompressorFactory)) + if (BuiltinZlibCompression.TryLoading(out var compressorFactory, out var decompressorFactory)) + Add(compressorFactory, decompressorFactory); + else if (ZlibCompression.TryLoading(out compressorFactory, out decompressorFactory)) Add(compressorFactory, decompressorFactory); } diff --git a/tests/DotPulsar.Tests/Internal/Compression/BuiltinZlibCompressionTests.cs b/tests/DotPulsar.Tests/Internal/Compression/BuiltinZlibCompressionTests.cs new file mode 100644 index 000000000..360250da9 --- /dev/null +++ b/tests/DotPulsar.Tests/Internal/Compression/BuiltinZlibCompressionTests.cs @@ -0,0 +1,37 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace DotPulsar.Tests.Internal.Compression; + +using DotPulsar.Internal.Compression; + +[Trait("Category", "Unit")] +public class BuiltinZlibCompressionTests +{ + [Fact] + public void Compression_GivenDataToCompressAndDecompress_ShouldReturnOriginalData() + { + // Arrange + var couldLoad = BuiltinZlibCompression.TryLoading(out var compressorFactory, out var decompressorFactory); + couldLoad.Should().BeTrue(); + using var compressor = compressorFactory!.Create(); + using var decompressor = decompressorFactory!.Create(); + + // Act + var compressionWorks = CompressionTester.TestCompression(compressorFactory, decompressorFactory); + + // Assert + compressionWorks.Should().BeTrue(); + } +}