Home » Caching » Honey, I Shrunk the Content: Experiments with a Custom Sitecore Cache and Compression

Honey, I Shrunk the Content: Experiments with a Custom Sitecore Cache and Compression

Sitecore Technology MVP 2016
Sitecore MVP 2015
Sitecore MVP 2014

Enter your email address to follow this blog and receive notifications of new posts by email.

A few days ago, I pondered whether there would be any utility in creating a custom Sitecore cache that compresses data before it’s stored and decompresses data upon retrieval. I wondered whether having such a cache would facilitate in conserving memory resources, thus curtailing the need to beef up servers from a memory perspective.

You’re probably thinking “Mike, who cares? Memory is cheaper today than ever before!” That thought is definitely valid.

However, I would argue we owe it to our clients and to ourselves as developers to push the envelope as much as possible by architecting our solutions to be as efficient and resource conscious as possible.

Plus, I was curious over how expensive compress/decompress operations would be for real-time requests.

The following code showcases my ventures into trying to answer these questions.

First, I defined an interface for compressors. Compressors must define methods to compress and decompress data (duh :)). I also added an additional Decompress method to cast the data object to a particular type — all for the purpose of saving client code the trouble of having to do their own type casting (yeah right, I really did it to be fancy).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors.Base
{
    public interface ICompressor
    {
        string Name { get; }

        byte[] Compress(object uncompressedData);

        T Decompress<T>(byte[] compressedData) where T : class;

        object Decompress(byte[] compressedData);

        long GetDataSize(object data);
    }
}

I decided to use two compression algorithms available in the System.IO.Compression namespace — Deflate and GZip. Since both algorithms within this namespace implement System.IO.Stream, I found an opportunity to use the Template method pattern.

In the spirit of this design pattern, I created an abstract class — see the CompressionStreamCompressor class below — which contains shared logic for compressing/decompressing data using methods defined by the Stream class. Subclasses only have to “fill in the blanks” by implementing the abstract method CreateNewCompressionStream — a method that returns a new instance of the compression stream represented by the subclass.

CompressionStreamCompressor:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

using Sitecore.Diagnostics;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors.Base
{
    public abstract class CompressionStreamCompressor : ICompressor
    {
        protected CompressionStreamCompressor()
        {
        }

        public virtual byte[] Compress(object uncompressedData)
        {
            Assert.ArgumentNotNull(uncompressedData, "uncompressedData");
            
            byte[] uncompressedBytes = ConvertObjectToBytes(uncompressedData);
            return Compress(uncompressedBytes);
        }

        private byte[] Compress(byte[] uncompressedData)
        {
            Assert.ArgumentNotNull(uncompressedData, "uncompressedData");

            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (Stream compressionStream = CreateNewCompressionStream(memoryStream, CompressionMode.Compress))
                {
                    compressionStream.Write(uncompressedData, 0, uncompressedData.Length);
                }

                return memoryStream.ToArray();
            }
        }

        public virtual T Decompress<T>(byte[] compressedData) where T : class
        {
            object decompressedData = Decompress(compressedData);
            return decompressedData as T;
        }

        public virtual object Decompress(byte[] compressedBytes)
        {
            Assert.ArgumentNotNull(compressedBytes, "compressedBytes");

            using (MemoryStream inputMemoryStream = new MemoryStream(compressedBytes))
            {
                using (Stream compressionStream = CreateNewCompressionStream(inputMemoryStream, CompressionMode.Decompress))
                {
                    using (MemoryStream outputMemoryStream = new MemoryStream())
                    {
                        compressionStream.CopyTo(outputMemoryStream);
                        return ConvertBytesToObject(outputMemoryStream.ToArray());
                    }
                }
            }
        }

        protected abstract Stream CreateNewCompressionStream(Stream stream, CompressionMode compressionMode);

        public long GetDataSize(object data)
        {
            if (data == null)
                return 0;

            IFormatter formatter = new BinaryFormatter();
            long size = 0;

            using (MemoryStream memoryStream = new MemoryStream())
            {
                formatter.Serialize(memoryStream, data);
                size = memoryStream.Length;
            }

            return size;
        }

        protected static byte[] ConvertObjectToBytes(object data)
        {
            if (data == null)
                return null;

            byte[] bytes = null;
            IFormatter formatter = new BinaryFormatter();

            using (MemoryStream memoryStream = new MemoryStream())
            {
                formatter.Serialize(memoryStream, data);
                bytes = memoryStream.ToArray();
            }
            
            return bytes;
        }

        protected static object ConvertBytesToObject(byte[] bytes)
        {
            if (bytes == null)
                return null;

            object deserialized = null;
            
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                IFormatter formatter = new BinaryFormatter();
                memoryStream.Position = 0;
                deserialized = formatter.Deserialize(memoryStream);
            }

            return deserialized;
        }
    }
}

DeflateCompressor:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Compression
{
    public class DeflateCompressor : CompressionStreamCompressor
    {
        private DeflateCompressor()
        {
        }

        protected override Stream CreateNewCompressionStream(Stream stream, CompressionMode compressionMode)
        {
            return new DeflateStream(stream, compressionMode, false);
        }

        public static ICompressor CreateNewCompressor()
        {
            return new DeflateCompressor();
        }
    }
}

GZipCompressor:

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Compression
{
    public class GZipCompressor : CompressionStreamCompressor
    {
        private GZipCompressor()
        {
        }

        protected override Stream CreateNewCompressionStream(Stream stream, CompressionMode compressionMode)
        {
            return new GZipStream(stream, compressionMode, false);
        }

        public static ICompressor CreateNewCompressor()
        {
            return new GZipCompressor();
        }
    }
}

If Microsoft ever decides to augment their arsenal of compression streams in System.IO.Compression, we could easily add new Compressor classes for these via the template method paradigm above — as long as these new compression streams implement System.IO.Stream.

After implementing the classes above, I decided I needed a “dummy” Compressor — a compressor that does not execute any compression algorithm but implements the ICompressor interface. My reasoning for doing so is to have a default Compressor be returned via a compressor factory (you will see that I created one further down), and also for ascertaining baseline benchmarks.

Plus, I figured it would be nice to have an object that closely follows the Null Object pattern — albeit in our case, we aren’t truly using this design pattern since our “Null” class is actually executing logic — so client code can avoid having null checks all over the place.

I had to go back and change my Compress and Decompress methods to be virtual in my abstract class so that I can override them within my “Null” Compressor class. The methods just take in the expected parameters and return expected types with no compression or decompression actions in the mix.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

using Sitecore.Diagnostics;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors
{
    class NullCompressor : CompressionStreamCompressor
    {
        private NullCompressor()
        {
        }

        public override byte[] Compress(object uncompressedData)
        {
            Assert.ArgumentNotNull(uncompressedData, "uncompressedData");
            return ConvertObjectToBytes(uncompressedData);
        }

        public override object Decompress(byte[] compressedBytes)
        {
            Assert.ArgumentNotNull(compressedBytes, "compressedBytes");
            return ConvertBytesToObject(compressedBytes);
        }

        protected override Stream CreateNewCompressionStream(Stream stream, CompressionMode compressionMode)
        {
            return null;
        }

        public static ICompressor CreateNewCompressor()
        {
            return new NullCompressor();
        }
    }
}

Next, I defined my custom Sitecore cache with its interface and settings Data transfer object.

An extremely important thing to keep in mind when creating a custom Sitecore cache is knowing you must subclass CustomCache in Sitecore.Caching — most methods that add or get from cache are protected methods, and you won’t have access to these unless you subclass this abstract class (I wasn’t paying attention when I built my cache for the first time, and had to go back to the drawing board when i discovered my code would not compile due to restricted access rights).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Caching;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Caching.DTO
{
    public class SquashedCacheSettings
    {
        public string Name { get; set; }
        public long MaxSize { get; set; }
        public ICompressor Compressor { get; set; }
        public bool CompressionEnabled { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Caching.Base
{
    public interface ISquashedCache
    {
        ICompressor Compressor { get; }

        void AddToCache(object key, object value);

        T GetFromCache<T>(object key) where T : class;

        object GetFromCache(object key);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Caching;
using Sitecore.Data;
using Sitecore.Diagnostics;

using Sitecore.Sandbox.Utilities.Caching.Base;
using Sitecore.Sandbox.Utilities.Caching.DTO;
using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;

namespace Sitecore.Sandbox.Utilities.Caching
{
    public class SquashedCache : CustomCache, ISquashedCache
    {
        private SquashedCacheSettings SquashedCacheSettings { get; set; }

        public ICompressor Compressor
        {
            get
            {
                return SquashedCacheSettings.Compressor;
            }
        }

        private SquashedCache(SquashedCacheSettings squashedCacheSettings)
            : base(squashedCacheSettings.Name, squashedCacheSettings.MaxSize)
        {
            SetSquashedCacheSettings(squashedCacheSettings);
        }

        private void SetSquashedCacheSettings(SquashedCacheSettings squashedCacheSettings)
        {
            SquashedCacheSettings = squashedCacheSettings;
        }

        public void AddToCache(object key, object value)
        {
            DataInformation dataInformation = GetCompressedDataInformation(value);
            SetObject(key, dataInformation.Data, dataInformation.Size);
        }

        private DataInformation GetCompressedDataInformation(object data)
        {
            long size = SquashedCacheSettings.Compressor.GetDataSize(data);
            return GetCompressedDataInformation(data, size);
        }

        private DataInformation GetCompressedDataInformation(object data, long size)
        {
            if (SquashedCacheSettings.CompressionEnabled)
            {
                data = SquashedCacheSettings.Compressor.Compress(data);
                size = SquashedCacheSettings.Compressor.GetDataSize(data);
            }

            return new DataInformation(data, size);
        }

        public T GetFromCache<T>(object key) where T : class
        {
            object value = GetFromCache(key);
            return value as T;
        }

        public object GetFromCache(object key)
        {
            byte[] value = (byte[])GetObject(key);
            return SquashedCacheSettings.Compressor.Decompress(value);
        }

        private T GetDecompressedData<T>(byte[] data) where T : class
        {
            if (SquashedCacheSettings.CompressionEnabled)
            {
                return SquashedCacheSettings.Compressor.Decompress<T>(data);
            }

            return data as T;
        }

        private object GetDecompressedData(byte[] data)
        {
            if (SquashedCacheSettings.CompressionEnabled)
            {
                return SquashedCacheSettings.Compressor.Decompress(data);
            }

            return data;
        }

        private struct DataInformation
        {
            public object Data;
            public long Size;

            public DataInformation(object data, long size)
            {
                Data = data;
                Size = size;
            }
        }

        public static ISquashedCache CreateNewSquashedCache(SquashedCacheSettings squashedCacheSettings)
        {
            AssertSquashedCacheSettings(squashedCacheSettings);
            return new SquashedCache(squashedCacheSettings);
        }

        private static void AssertSquashedCacheSettings(SquashedCacheSettings squashedCacheSettings)
        {
            Assert.ArgumentNotNull(squashedCacheSettings, "squashedCacheSettings");
            Assert.ArgumentNotNullOrEmpty(squashedCacheSettings.Name, "squashedCacheSettings.Name");
            Assert.ArgumentCondition(squashedCacheSettings.MaxSize > 0, "squashedCacheSettings.MaxSize", "MaxSize must be greater than zero.");
            Assert.ArgumentNotNull(squashedCacheSettings.Compressor, "squashedCacheSettings.Compressor");
        }
    }
}

You’re probably thinking “Mike, what’s up with the name SquashedCache”? Well, truth be told, I was thinking about Thanksgiving here in the United States — it’s just around the corner — and how squash is a usually found on the table for Thanksgiving dinner. The name SquashedCache just fit in perfectly in the spirit of our Thanksgiving holiday.

However, the following class names were considered.

public class SirSquishALot
{
}

public class MiniMeCache
{
}

// this became part of this post’s title instead 🙂
public class HoneyIShrunkTheContent
{
}

I figured having a Factory class for compressor objects would offer a clean and central place for creating them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors.Enums
{
    public enum CompressorType
    {
        Deflate,
        GZip,
        Null
    } 
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Enums;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors.Base
{
    public interface ICompressorFactory
    {
        ICompressor CreateNewCompressor(CompressorType compressorType);
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;
using Sitecore.Sandbox.Utilities.Compression.Compressors.Enums;

namespace Sitecore.Sandbox.Utilities.Compression.Compressors
{
    public class CompressorFactory : ICompressorFactory
    {
        private CompressorFactory()
        {
        }

        public ICompressor CreateNewCompressor(CompressorType compressorType)
        {
            if (compressorType == CompressorType.Deflate)
            {
                return DeflateCompressor.CreateNewCompressor();
            }
            else if (compressorType == CompressorType.GZip)
            {
                return GZipCompressor.CreateNewCompressor();
            }

            return NullCompressor.CreateNewCompressor();
        }

        public static ICompressorFactory CreateNewCompressorFactory()
        {
            return new CompressorFactory();
        }
    }
}

Now, it’s time to test everything above and look at some statistics. I basically created a sublayout containing some repeaters to highlight how each compressor performed against the others — including the “Null” compressor which serves as the baseline.

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Squash Test.ascx.cs" Inherits="Sitecore650rev120706.layouts.sublayouts.Squash_Test" %>

<div>
    <h2><u>Compression Test</u></h2>
    Uncompressed Size: <asp:Literal ID="litUncompressedSize" runat="server" /> bytes
    <asp:Repeater ID="rptCompressionTest" runat="server">
        <HeaderTemplate>
            <br /><br />
        </HeaderTemplate>
        <ItemTemplate>
                <div>
                    Compressed Size using <%# Eval("TestName")%>: <%# Eval("CompressedSize")%> bytes <br />
                    Compression Ratio using <%# Eval("TestName")%>: <%# Eval("CompressionRatio","{0:p}") %> of original size
                </div>
        </ItemTemplate>
        <SeparatorTemplate>
            <br />
        </SeparatorTemplate>
    </asp:Repeater>
</div>

<asp:Repeater ID="rptAddToCacheTest" runat="server">
    <HeaderTemplate>
        <div>
            <h2><u>AddToCache() Test</u></h2>
    </HeaderTemplate>
    <ItemTemplate>
            <div>
                AddToCache() Elasped Time for <%# Eval("TestName")%>: <%# Eval("ElapsedMilliseconds")%> ms
            </div>
    </ItemTemplate>
    <FooterTemplate>
        </div>
    </FooterTemplate>
</asp:Repeater>

<asp:Repeater ID="rptGetFromCacheTest" runat="server">
    <HeaderTemplate>
        <div>
            <h2><u>GetFromCache() Test</u></h2>
    </HeaderTemplate>
    <ItemTemplate>
            <div>
                GetFromCache() Elasped Time for <%# Eval("TestName")%>: <%# Eval("ElapsedMilliseconds")%> ms
            </div>
    </ItemTemplate>
    <FooterTemplate>
        </div>
    </FooterTemplate>
</asp:Repeater>

<asp:Repeater ID="rptDataIntegrityTest" runat="server">
    <HeaderTemplate>
        <div>
            <h2><u>Data Integrity Test</u></h2>
    </HeaderTemplate>
    <ItemTemplate>
            <div>
                Data Retrieved From <%# Eval("TestName")%> Equals Original: <%# Eval("AreEqual")%>
            </div>
    </ItemTemplate>
    <FooterTemplate>
        </div>
    </FooterTemplate>
</asp:Repeater>

In my code-behind, I’m grabbing the full text of the book War and Peace by Leo Tolstoy for testing purposes. The full text of this copy of the book is over 3.25 MB.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using Sitecore;

using Sitecore.Sandbox.Utilities.Caching;
using Sitecore.Sandbox.Utilities.Caching.Base;
using Sitecore.Sandbox.Utilities.Caching.DTO;

using Sitecore.Sandbox.Utilities.Compression.Compressors;
using Sitecore.Sandbox.Utilities.Compression.Compressors.Base;
using Sitecore.Sandbox.Utilities.Compression.Compressors.Enums;

namespace Sitecore650rev120706.layouts.sublayouts
{
    public partial class Squash_Test : System.Web.UI.UserControl
    {
        private const string CacheKey = "War and Peace";
        private const string WarAndPeaceUrl = "http://www.gutenberg.org/cache/epub/2600/pg2600.txt";
        private const string MaxSize = "50MB";

        private ICompressorFactory _Factory;
        private ICompressorFactory Factory
        {
            get
            {
                if(_Factory == null)
                    _Factory = CompressorFactory.CreateNewCompressorFactory();

                return _Factory;
            }
        }

        private IEnumerable<ISquashedCache> _SquashedCaches;
        private IEnumerable<ISquashedCache> SquashedCaches
        {
            get
            {
                if(_SquashedCaches == null)
                    _SquashedCaches = CreateAllSquashedCaches();

                return _SquashedCaches;
            }
        }

        private string _WarAndPeaceText;
        private string WarAndPeaceText
        {
            get
            {
                if (string.IsNullOrEmpty(_WarAndPeaceText))
                    _WarAndPeaceText = GetWarAndPeaceText();

                return _WarAndPeaceText;
            }
        }

        private long _UncompressedSize;
        private long UncompressedSize
        {
            get
            {
                if(_UncompressedSize == 0)
                    _UncompressedSize = GetUncompressedSize();

                return _UncompressedSize;
            }
        }


        private Stopwatch _Stopwatch;
        private Stopwatch Stopwatch
        {
            get
            {
                if (_Stopwatch == null)
                    _Stopwatch = Stopwatch.StartNew();

                return _Stopwatch;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            SetUncomopressedSizeLiteral();
            BindAllRepeaters();
        }

        private void SetUncomopressedSizeLiteral()
        {
            litUncompressedSize.Text = UncompressedSize.ToString();
        }

        private void BindAllRepeaters()
        {
            BindCompressionTestRepeater();
            BindAddToCacheTestRepeater();
            BindGetFromCacheTestRepeater();
            BindDataIntegrityTestRepeater();
        }

        private void BindCompressionTestRepeater()
        {
            rptCompressionTest.DataSource = GetCompressionTestData();
            rptCompressionTest.DataBind();
        }

        private IEnumerable<CompressionRatioAtom> GetCompressionTestData()
        {
            IList<CompressionRatioAtom> compressionRatioAtoms = new List<CompressionRatioAtom>();

            foreach (ISquashedCache squashedCache in SquashedCaches)
            {
                byte[] compressed = squashedCache.Compressor.Compress(WarAndPeaceText);
                long compressedSize = squashedCache.Compressor.GetDataSize(compressed);

                CompressionRatioAtom compressionRatioAtom = new CompressionRatioAtom
                {
                    TestName = squashedCache.Name,
                    CompressedSize = compressedSize,
                    CompressionRatio = ((decimal)compressedSize / UncompressedSize)
                };

                compressionRatioAtoms.Add(compressionRatioAtom);
            }

            return compressionRatioAtoms;
        }

        private void BindAddToCacheTestRepeater()
        {
            rptAddToCacheTest.DataSource = GetAddToCacheTestData();
            rptAddToCacheTest.DataBind();
        }

        private IEnumerable<TimeTestAtom> GetAddToCacheTestData()
        {
            IList<TimeTestAtom> timeTestAtoms = new List<TimeTestAtom>();

            foreach (ISquashedCache squashedCache in SquashedCaches)
            {
                Stopwatch.Start();
                squashedCache.AddToCache(CacheKey, WarAndPeaceText);
                Stopwatch.Stop();

                TimeTestAtom timeTestAtom = new TimeTestAtom
                {
                    TestName = squashedCache.Name,
                    ElapsedMilliseconds = Stopwatch.Elapsed.TotalMilliseconds
                };

                timeTestAtoms.Add(timeTestAtom);
            }

            return timeTestAtoms;
        }

        private void BindGetFromCacheTestRepeater()
        {
            rptGetFromCacheTest.DataSource = GetGetFromCacheTestData();
            rptGetFromCacheTest.DataBind();
        }

        private IEnumerable<TimeTestAtom> GetGetFromCacheTestData()
        {
            IList<TimeTestAtom> timeTestAtoms = new List<TimeTestAtom>();

            foreach (ISquashedCache squashedCache in SquashedCaches)
            {
                Stopwatch.Start();
                squashedCache.GetFromCache<string>(CacheKey);
                Stopwatch.Stop();

                TimeTestAtom timeTestAtom = new TimeTestAtom
                {
                    TestName = squashedCache.Name,
                    ElapsedMilliseconds = Stopwatch.Elapsed.TotalMilliseconds
                };

                timeTestAtoms.Add(timeTestAtom);
            }

            return timeTestAtoms;
        }

        private void BindDataIntegrityTestRepeater()
        {
            rptDataIntegrityTest.DataSource = GetDataIntegrityTestData();
            rptDataIntegrityTest.DataBind();
        }

        private IEnumerable<DataIntegrityTestAtom> GetDataIntegrityTestData()
        {
            IList<DataIntegrityTestAtom> dataIntegrityTestAtoms = new List<DataIntegrityTestAtom>();

            foreach (ISquashedCache squashedCache in SquashedCaches)
            {
                string cachedContent = squashedCache.GetFromCache<string>(CacheKey);

                DataIntegrityTestAtom dataIntegrityTestAtom = new DataIntegrityTestAtom
                {
                    TestName = squashedCache.Name,
                    AreEqual = cachedContent == WarAndPeaceText
                };

                dataIntegrityTestAtoms.Add(dataIntegrityTestAtom);
            }

            return dataIntegrityTestAtoms;
            
        }

        private IEnumerable<ISquashedCache> CreateAllSquashedCaches()
        {
            IList<ISquashedCache> squashedCaches = new List<ISquashedCache>();
            squashedCaches.Add(CreateNewNullSquashedCache());
            squashedCaches.Add(CreateNewDeflateSquashedCache());
            squashedCaches.Add(CreateNewGZipSquashedCache());
            return squashedCaches;
        }

        private ISquashedCache CreateNewNullSquashedCache()
        {
            return CreateNewSquashedCache("Null Cache", MaxSize, CompressorType.Null);
        }

        private ISquashedCache CreateNewDeflateSquashedCache()
        {
            return CreateNewSquashedCache("Deflate Cache", MaxSize, CompressorType.Deflate);
        }

        private ISquashedCache CreateNewGZipSquashedCache()
        {
            return CreateNewSquashedCache("GZip Cache", MaxSize, CompressorType.GZip);
        }

        private ISquashedCache CreateNewSquashedCache(string cacheName, string maxSize, CompressorType compressorType)
        {
            SquashedCacheSettings squashedCacheSettings = CreateNewSquashedCacheSettings(cacheName, maxSize, compressorType);
            return SquashedCache.CreateNewSquashedCache(squashedCacheSettings);
        }

        private SquashedCacheSettings CreateNewSquashedCacheSettings(string cacheName, string maxSize, CompressorType compressorType)
        {
            return new SquashedCacheSettings
            {
                Name = cacheName,
                MaxSize = StringUtil.ParseSizeString(maxSize),
                Compressor = Factory.CreateNewCompressor(compressorType),
                CompressionEnabled = true
            };
        }

        private static string GetWarAndPeaceText()
        {
            WebRequest webRequest = (HttpWebRequest)WebRequest.Create(WarAndPeaceUrl);
            HttpWebResponse httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
            
            string warAndPeaceText = string.Empty;

            using (StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream()))
            {
                warAndPeaceText = streamReader.ReadToEnd();
            }

            return warAndPeaceText;
        }

        private long GetUncompressedSize()
        {
            return SquashedCaches.FirstOrDefault().Compressor.GetDataSize(WarAndPeaceText);
        }

        private class CompressionRatioAtom
        {
            public string TestName { get; set; }
            public long CompressedSize { get; set; }
            public decimal CompressionRatio { get; set; }
        }

        private class TimeTestAtom
        {
            public string TestName { get; set; }
            public double ElapsedMilliseconds { get; set; }
        }

        private class DataIntegrityTestAtom
        {
            public string TestName { get; set; }
            public bool AreEqual { get; set; }
        }
    }
}

From my screenshot below, we can see that both compression algorithms compress War and Peace down to virtually the same ratio of the original size.

Plus, the add operations are quite expensive for the true compressors over the “Null” compressor — GZip yielding the worst performance next to the others.

However, the get operations don’t appear to be that far off from each other — albeit I cannot truly conclude this I am only showing one test outcome here. It would be best to make such assertions after performing load testing to truly ascertain the performance of these algorithms. My ventures here were only to acquire a rough sense of how these algorithms perform.

In the future, I may want to explore how other compression algorithms stack up next to the two above. LZF — a real-time compression algorithm that promises good performance — is one algorithm I’m considering.

I will let you know my results if I take this algorithm for a dry run.

Gobble Gobble!

Advertisement

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: