4
\$\begingroup\$

I ported a lz77 implementation from java to c#, and it's working fine. Now I want it to compress bytes and not strings, as I need it for another project.

This is the porting, operating on string:

public class StringLZ77
{
    private char _referencePrefix;
    private int _referenceIntBase;
    private int _referenceIntFloorCode;
    private int _referenceIntCeilCode;
    private int _maxStringDistance;
    private int _minStringLength;
    private int _maxStringLength;
    private int _defaultWindowLength;
    private int _maxWindowLength;

    public StringLZ77()
    {
        _referencePrefix = '`';
        _referenceIntBase = 96;
        _referenceIntFloorCode = (int)' ';
        _referenceIntCeilCode = _referenceIntFloorCode + _referenceIntBase;
        _maxStringDistance = (int)Math.Pow(_referenceIntBase, 2) - 1;
        _minStringLength = 5;
        _maxStringLength = (int)Math.Pow(_referenceIntBase, 1) - 1
                + _minStringLength;
        _defaultWindowLength = 9220;
        _maxWindowLength = _maxStringDistance + _minStringLength;
    }

    public static string CompressStr(string data)
    {
        StringLZ77 lz = new StringLZ77();
        return lz.Compress(data, -1);
    }
    public static string DecompressStr(string data)
    {
        StringLZ77 lz = new StringLZ77();
        return lz.Decompress(data);
    }

    public string Compress(string data)
    {
        return Compress(data, -1);
    }

    public string Compress(string data, int windowLength)
    {
        if (windowLength == -1)
        {
            windowLength = _defaultWindowLength;
        }

        if (windowLength > _maxWindowLength)
        {
            throw new ArgumentException("Window length is too large.");
        }

        string compressed = string.Empty;

        int pos = 0;
        int lastPos = data.Length - _minStringLength;

        while (pos < lastPos)
        {
            Stopwatch w = Stopwatch.StartNew();

            int searchStart = Math.Max(pos - windowLength, 0);
            int matchLength = _minStringLength;
            bool foundMatch = false;
            int bestMatchDistance = _maxStringDistance;
            int bestMatchLength = 0;
            string newCompressed = null;

            while ((searchStart + matchLength) < pos)
            {
                int sourceWindowEnd = Math.Min(searchStart + matchLength, data.Length);
                int targetWindowEnd = Math.Min(pos + matchLength, data.Length);

                string m1 = data.Substring(searchStart, sourceWindowEnd - searchStart);
                string m2 = data.Substring(pos, targetWindowEnd - pos);

                bool isValidMatch = m1.Equals(m2) && matchLength < _maxStringLength;

                if (isValidMatch)
                {
                    matchLength++;
                    foundMatch = true;
                }
                else
                {
                    int realMatchLength = matchLength - 1;

                    if (foundMatch && (realMatchLength > bestMatchLength))
                    {
                        bestMatchDistance = pos - searchStart - realMatchLength;
                        bestMatchLength = realMatchLength;
                    }

                    matchLength = _minStringLength;
                    searchStart++;
                    foundMatch = false;
                }
            }

            if (bestMatchLength != 0)
            {
                newCompressed = _referencePrefix
                        + EncodeReferenceInt(bestMatchDistance, 2)
                        + EncodeReferenceLength(bestMatchLength);

                pos += bestMatchLength;
            }
            else
            {
                if (data[pos] != _referencePrefix)
                {
                    newCompressed = string.Empty + data[pos];
                }
                else
                {
                    newCompressed = string.Empty + _referencePrefix + _referencePrefix;
                }

                pos++;
            }

            compressed += newCompressed;

            Console.WriteLine("{0}", w.ElapsedMilliseconds);
        }

        return compressed + data.Substring(pos).Replace(_referencePrefix.ToString(), string.Format("{0}{1}", _referencePrefix, _referencePrefix));
    }

    public string Decompress(string data)
    {
        string decompressed = "";
        int pos = 0;

        while (pos < data.Length)
        {

            char currentChar = data[pos];

            if (currentChar != _referencePrefix)
            {
                decompressed += currentChar;
                pos++;
            }
            else
            {

                char nextChar = data[pos + 1];

                if (nextChar != _referencePrefix)
                {

                    int distance = DecodeReferenceInt(data.Substring(pos + 1, 2), 2);
                    int length = DecodeReferenceLength(data.Substring(pos + 3, 1));
                    int start = decompressed.Length - distance - length;
                    int end = start + length;

                    decompressed += decompressed.Substring(start, end - start);
                    pos += _minStringLength - 1;

                }
                else
                {
                    decompressed += _referencePrefix;
                    pos += 2;
                }
            }
        }

        return decompressed;
    }

    private string EncodeReferenceInt(int value, int width)
    {
        if ((value >= 0) && (value < (Math.Pow(_referenceIntBase, width) - 1)))
        {
            string encoded = string.Empty;

            while (value > 0)
            {
                char c = (char)((value % _referenceIntBase) + _referenceIntFloorCode);
                encoded = string.Format("{0}{1}", c, encoded);//string.Empty + c + encoded;
                value = (int)Math.Floor((double)value / _referenceIntBase);
            }

            int missingLength = width - encoded.Length;

            for (int i = 0; i < missingLength; i++)
            {
                char c = (char)_referenceIntFloorCode;
                encoded = string.Empty + c + encoded;
            }

            return encoded;
        }
        else
        {
            throw new ArgumentException(string.Format("Reference int out of range: {0} (width = {1})", value, width));
        }
    }

    private string EncodeReferenceLength(int length)
    {
        return EncodeReferenceInt(length - _minStringLength, 1);
    }

    private int DecodeReferenceInt(string data, int width)
    {
        int value = 0;

        for (int i = 0; i < width; i++)
        {

            value *= _referenceIntBase;

            int charCode = (int)data[i];

            if ((charCode >= _referenceIntFloorCode)
                    && (charCode <= _referenceIntCeilCode))
            {

                value += charCode - _referenceIntFloorCode;

            }
            else
            {

                throw new ArgumentException("Invalid char code in reference int: " + charCode);
            }
        }

        return value;
    }

    private int DecodeReferenceLength(string data)
    {
        return DecodeReferenceInt(data, 1) + _minStringLength;
    }
}

While this is my implementation on bytes:

internal class LZ77
{
    private byte _referencePrefix;
    private int _referenceIntBase;
    private int _referenceIntFloorCode;
    private int _referenceIntCeilCode;
    private int _maxStringDistance;
    private int _minStringLength;
    private int _maxStringLength;
    private int _defaultWindowLength;
    private int _maxWindowLength;

    public LZ77()
    {
        _referencePrefix = (byte)'`';
        _referenceIntBase = 96;
        _referenceIntFloorCode = 32;
        _referenceIntCeilCode = 128;
        _maxStringDistance = (int)Math.Pow(_referenceIntBase, 2) - 1;
        _minStringLength = 5;
        _maxStringLength = (int)Math.Pow(_referenceIntBase, 1) - 1 + _minStringLength;
        _defaultWindowLength = CompressionLevel.Low;
        _maxWindowLength = _maxStringDistance + _minStringLength;
    }

    public byte[] Compress(byte[] data, int windowLength)
    {
        if (windowLength == -1)
        {
            windowLength = _defaultWindowLength;
        }

        if (windowLength > _maxWindowLength)
        {
            throw new ArgumentException("Window length is too large.");
        }

        List<byte> compressed = new List<byte>();

        int pos = 0;
        int lastPos = data.Length - _minStringLength;

        while (pos < lastPos)
        {
            //Stopwatch w = Stopwatch.StartNew();

            int searchStart = Math.Max(pos - windowLength, 0);
            int matchLength = _minStringLength;
            bool foundMatch = false;
            int bestMatchDistance = _maxStringDistance;
            int bestMatchLength = 0;
            List<byte> newCompressed = new List<byte>();

            while ((searchStart + matchLength) < pos)
            {
                int sourceWindowEnd = Math.Min(searchStart + matchLength, data.Length);
                int targetWindowEnd = Math.Min(pos + matchLength, data.Length);

                IList<byte> m1 = data.Skip(searchStart).Take(sourceWindowEnd - searchStart).ToList();
                IList<byte> m2 = data.Skip(pos).Take(targetWindowEnd - pos).ToList();

                bool isValidMatch = m1.SequenceEqual(m2) && matchLength < _maxStringLength;

                if (isValidMatch)
                {
                    matchLength++;
                    foundMatch = true;
                }
                else
                {
                    int realMatchLength = matchLength - 1;

                    if (foundMatch && (realMatchLength > bestMatchLength))
                    {
                        bestMatchDistance = pos - searchStart - realMatchLength;
                        bestMatchLength = realMatchLength;
                    }

                    matchLength = _minStringLength;
                    searchStart++;
                    foundMatch = false;
                }
            }

            if (bestMatchLength != 0)
            {
                newCompressed.Add(_referencePrefix);
                newCompressed.AddRange(EncodeReferenceInt(bestMatchDistance, 2));
                newCompressed.AddRange(EncodeReferenceLength(bestMatchLength));

                pos += bestMatchLength;
            }
            else
            {
                if (data[pos] != _referencePrefix)
                {
                    newCompressed = new List<byte>(new byte[] { data[pos] });
                }
                else
                {
                    newCompressed = new List<byte>(new byte[] { _referencePrefix, _referencePrefix });
                }

                pos++;
            }

            compressed.AddRange(newCompressed);

            //Console.WriteLine(w.ElapsedMilliseconds);
        }

        var lasts = data.Skip(pos).Take(data.Length - pos);
        lasts
            .Where(x => x == _referencePrefix)
            .ToList()
            .ForEach
            (
                x =>
                    compressed
                        .AddRange
                        (
                            new byte[]
                            {
                                _referencePrefix,
                                _referencePrefix
                            }
                        )
            );

        return compressed.ToArray();
    }

    public byte[] Decompress(byte[] data)
    {
        List<byte> decompressed = new List<byte>();
        int pos = 0;

        while (pos < data.Length)
        {
            byte currentByte = data[pos];

            if (currentByte != _referencePrefix)
            {
                decompressed.Add(currentByte);
                pos++;
            }
            else
            {
                byte nextChar = data[pos + 1];

                if (nextChar != _referencePrefix)
                {
                    int distance = DecodeReferenceInt(data.Skip(pos + 1).Take(2).ToList(), 2);
                    int length = DecodeReferenceLength(data.Skip(pos + 3).Take(1).ToList());
                    int start = decompressed.Count - distance - length;
                    int end = start + length;

                    var slice = decompressed.Skip(start).Take(end - start).ToList();
                    decompressed.AddRange(slice);
                    pos += _minStringLength - 1;
                }
                else
                {
                    decompressed.Add((byte)_referencePrefix);
                    pos += 2;
                }
            }
        }

        return decompressed.ToArray();
    }

    private IList<byte> EncodeReferenceInt(int value, int width)
    {
        if ((value >= 0) && (value < (Math.Pow(_referenceIntBase, width) - 1)))
        {
            IList<byte> encoded = new List<byte>();

            while (value > 0)
            {
                byte b = (byte)((value % _referenceIntBase) + _referenceIntFloorCode);
                encoded.Insert(0, b);
                value = (int)Math.Floor((double)value / _referenceIntBase);
            }

            int missingLength = width - encoded.Count;

            for (int i = 0; i < missingLength; i++)
            {
                byte b = (byte)_referenceIntFloorCode;
                encoded.Insert(0, b);
            }

            return encoded;
        }
        else
        {
            throw new ArgumentException(string.Format("Reference int out of range: {0} (width = {1})", value, width));
        }
    }

    private IList<byte> EncodeReferenceLength(int length)
    {
        return EncodeReferenceInt(length - _minStringLength, 1);
    }

    private int DecodeReferenceInt(IList<byte> data, int width)
    {
        int value = 0;

        for (int i = 0; i < width; i++)
        {
            value *= _referenceIntBase;

            int charCode = (int)data[i];

            if ((charCode >= _referenceIntFloorCode) && (charCode <= _referenceIntCeilCode))
            {
                value += charCode - _referenceIntFloorCode;
            }
            else
            {
                throw new ArgumentException("Invalid char code in reference int: " + charCode);
            }
        }

        return value;
    }

    private int DecodeReferenceLength(IList<byte> data)
    {
        return DecodeReferenceInt(data, 1) + _minStringLength;
    }
}

On small data they both work fine. But I tried to compress 64kb of data and the one compressing byte is endless. My opinion is because of the data-slicing at the beginning of the second while:

IList<byte> m1 = data.Skip(searchStart).Take(sourceWindowEnd - searchStart).ToList();
IList<byte> m2 = data.Skip(pos).Take(targetWindowEnd - pos).ToList();

Those .Skip().Take() are too slow for thousands of cycles. Anyway any improvements are well accepted.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Besides any other improvement you may/should apply...first of all get rid of LINQ there. That's an useless overhead (at best). You just need a CompareArrayParts() function with a plain for cycle. No IEnumerable wrappers, function calls and enumerations (and of course no ToList() to create an expansive copy of data blocks). Both m1 and m2 serve only the purpose to be compared with SequenceEquals(). Replace it with a for. \$\endgroup\$ Nov 4, 2015 at 15:05
  • \$\begingroup\$ Lz77 was designed to compress text, why do you expect it to work well on raw bytes? \$\endgroup\$
    – RubberDuck
    Nov 4, 2015 at 17:27
  • \$\begingroup\$ @RubberDuck Whether it will work on bytes entirely depends on what those bytes are. For example, if they were a tar archive of text files, it will work fine. \$\endgroup\$
    – svick
    Nov 4, 2015 at 23:58
  • \$\begingroup\$ @RubberDuck let me remind you that lz77 is used in the Deflate algorithm, which is used for zip, rar, gzip, exc.. \$\endgroup\$
    – Phate01
    Nov 5, 2015 at 13:35
  • 1
    \$\begingroup\$ @Heslacher ok, I'll ask a new question! \$\endgroup\$
    – Phate01
    Nov 6, 2015 at 16:11

2 Answers 2

1
\$\begingroup\$

My problem were these LINQ

IList<byte> m1 = data.Skip(searchStart).Take(sourceWindowEnd - searchStart).ToList();
IList<byte> m2 = data.Skip(pos).Take(targetWindowEnd - pos).ToList();

which I replaced with

int m1Length = sourceWindowEnd - searchStart;
int m2Length = targetWindowEnd - pos;

byte[] m1 = new byte[m1Length];
byte[] m2 = new byte[m2Length];

Array.Copy(data, searchStart, m1, 0, m1Length);
Array.Copy(data, pos, m2, 0, m2Length);

Now it work almost as fast as the string comrpessor

\$\endgroup\$
4
  • \$\begingroup\$ You should also consider to...do not perform any copy. Check first line after those two incriminated LINQ queries: it's another LINQ extension method to compare those lists. For a comparison you do not need to create two new buffers! Let's do it with a plain for! \$\endgroup\$ Nov 6, 2015 at 16:18
  • \$\begingroup\$ Problem is that .Skip().Take() is super slow (more that 1 minute!), even working lazily with IEnumerable, I just tested it. \$\endgroup\$
    – Phate01
    Nov 6, 2015 at 16:26
  • \$\begingroup\$ Well, many LINQ functions have special cases for arrays but Skip() and Take() AFAIK have not because it's pretty easy to rewrite with for. Concept: for (int i=0; i < m1Length; ++i) { if (data[searchStart + i] != data[pos + i] return false; }. .NET memory allocator is pretty fast but you're creating many copies just to perform a comparison. Note that m1Length and m2Length must be equal (it's what `SequenceEqual() checks first), if not you can directly skip any other operation. \$\endgroup\$ Nov 6, 2015 at 16:42
  • \$\begingroup\$ It worked! Now it's super fast! Thank you very much \$\endgroup\$
    – Phate01
    Nov 6, 2015 at 17:04
1
\$\begingroup\$

There are a few things that in my opinion should be changed.

Design

The class' private fields seem to be only read (except the constructor which initializes them). In this case they should be transformed into constants or readonly fields. After you've transformed them into constant/readonly fields you should transform them into properties. The end result of this step should change the fields into something like the following:

private readonly int _referenceIntBase = 96;
public int ReferenceIntBase
{
    get { return _referenceIntBase; }
}

private readonly int _referenceIntFloorCode = (int)' ';
public int ReferenceIntFloorCode
{
    get { return _referenceIntFloorCode; }
}

public int ReferenceIntCeilCode 
{
    get { return ReferenceIntBase + ReferenceIntFloorCode; }
}

// and so on with the other fields

Another thing from the design point of view: The class doesn't seem to need instance methods. You don't actually need to create StringLZ77 or LZ77 objects (at least, so it seems). In that case the class should be declared static and the constructor goes away and the actual instance methods and properties have to be transformed to static ones (the current static methods just create a StringLZ77 object that calls the instance methods anyway).

Regarding the Stopwatch object, what's it doing there? Is it actually needed for the algorithm? If it's there for debugging purposes use it in #if DEBUG ... #endif directives. The same for the Console.WriteLine() bit.

Performance

Regarding the performance bottleneck on m1 and m2 I'd suggest two changes (besides changing the name into something more meaningful):

  • Declare IList<byte> m1 and IList<byte> m2 out of the cycle and instantiate them in the cycle. The same goes for the other variables that are declared inside the cycle.
  • Change the type from IList<byte> to IEnumerable<byte> so you don't have to enumerate all the items before you actually use them. In this way you don't have to call ToList() in the end (which is what enumerates the elements).

You declare variables inside cycles in a lot of cases. Bring the declaration outside of the cycles and assign them values in the cycles, in this way you create the object only once.

Also, don't concatenate strings using the + operator when you are in a cycle. Use the StringBuilder object.

You also do some operations that are not optimal, like (int)Math.Pow(_referenceIntBase, 1) which is equal to _referenceIntBase. Also, (int)Math.Pow(_referenceIntBase, 2) - 1 can be written as _referenceIntBase * _referenceIntBase - 1 which could also be put inside a check { ... } block to catch overflows if it's needed.

Regarding the List objects, try to replace them with IEnumerables. In the case of the ForEach method that you use in the code, you could just define a ForEach extension method for IEnumerables.

After this refactoring the code would look like the following:

public static class EnumerableExtensions
{
    public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }
}

internal static class LZ77
{
    private static readonly byte _referencePrefix = (byte)'`';
    public static byte ReferencePrefix { get { return _referencePrefix; } }

    private static readonly int _referenceIntBase = 96;
    public static int ReferenceIntBase { get { return _referenceIntBase; } }

    private static readonly int _referenceIntFloorCode = 32;
    public static int ReferenceIntFloorCode { get { return _referenceIntFloorCode; } }

    public static int ReferenceIntCeilCode { get { return ReferenceIntBase + ReferenceIntFloorCode; } }

    public static int MaxStringDistance { get { return _referenceIntBase * _referenceIntBase - 1; } }

    private static readonly int _minStringLength = 5;
    public static int MinStringLength { get { return _minStringLength; } }

    public static int MaxStringLength { get { return _referenceIntBase - 1 + _minStringLength; } }

    private static readonly int _defaultWindowLength = CompressionLevel.Low;
    public static int DefaultWindowLength { get { return _defaultWindowLength; } }

    public static int MaxWindowLength { get { return MaxStringDistance + _minStringLength; } }

    public static byte[] Compress(byte[] data, int windowLength)
    {
        if (windowLength == -1)
        {
            windowLength = DefaultWindowLength;
        }

        if (windowLength > MaxWindowLength)
        {
            throw new ArgumentException("Window length is too large.");
        }

        List<byte> compressed = new List<byte>();
        int pos = 0;
        int lastPos = data.Length - _minStringLength;
        int searchStart, matchLength, bestMatchDistance, bestMatchLength, 
            sourceWindowEnd, targetWindowEnd, realMatchLength;
        bool foundMatch, isValidMatch;
        List<byte> newCompressed;
        IEnumerable<byte> m1, m2;

        while (pos < lastPos)
        {
            searchStart = Math.Max(pos - windowLength, 0);
            matchLength = _minStringLength;
            foundMatch = false;
            bestMatchDistance = MaxStringDistance;
            bestMatchLength = 0;
            newCompressed = new List<byte>();

            while ((searchStart + matchLength) < pos)
            {
                sourceWindowEnd = Math.Min(searchStart + matchLength, data.Length);
                targetWindowEnd = Math.Min(pos + matchLength, data.Length);
                m1 = data.Skip(searchStart).Take(sourceWindowEnd - searchStart);
                m2 = data.Skip(pos).Take(targetWindowEnd - pos);
                isValidMatch = m1.SequenceEqual(m2) && matchLength < MaxStringLength;

                if (isValidMatch)
                {
                    matchLength++;
                    foundMatch = true;
                }
                else
                {
                    realMatchLength = matchLength - 1;

                    if (foundMatch && (realMatchLength > bestMatchLength))
                    {
                        bestMatchDistance = pos - searchStart - realMatchLength;
                        bestMatchLength = realMatchLength;
                    }

                    matchLength = _minStringLength;
                    searchStart++;
                    foundMatch = false;
                }
            }

            if (bestMatchLength != 0)
            {
                newCompressed.Add(ReferencePrefix);
                newCompressed.AddRange(EncodeReferenceInt(bestMatchDistance, 2));
                newCompressed.AddRange(EncodeReferenceLength(bestMatchLength));

                pos += bestMatchLength;
            }
            else
            {
                if (data[pos] != _referencePrefix)
                {
                    newCompressed = new List<byte>(new byte[] { data[pos] });
                }
                else
                {
                    newCompressed = new List<byte>(new byte[] { _referencePrefix, _referencePrefix });
                }

                pos++;
            }

            compressed.AddRange(newCompressed);
        }

        data.Skip(pos)
            .Take(data.Length - pos)
            .Where(x => x == _referencePrefix)
            .ForEach
            (
                x => compressed
                        .AddRange
                        (
                            new byte[]
                            {
                                _referencePrefix,
                                _referencePrefix
                            }
                        )
            );

        return compressed.ToArray();
    }

    public static byte[] Decompress(byte[] data)
    {
        List<byte> decompressed = new List<byte>();
        int pos = 0;
        byte currentByte, nextChar;
        int distance, length, start, end;

        while (pos < data.Length)
        {
            currentByte = data[pos];

            if (currentByte != _referencePrefix)
            {
                decompressed.Add(currentByte);
                pos++;
            }
            else
            {
                nextChar = data[pos + 1];

                if (nextChar != _referencePrefix)
                {
                    distance = DecodeReferenceInt(data.Skip(pos + 1).Take(2).ToList(), 2);
                    length = DecodeReferenceLength(data.Skip(pos + 3).Take(1).ToList());
                    start = decompressed.Count - distance - length;
                    end = start + length;

                    decompressed.AddRange(decompressed.Skip(start).Take(end - start));
                    pos += _minStringLength - 1;
                }
                else
                {
                    decompressed.Add((byte)_referencePrefix);
                    pos += 2;
                }
            }
        }

        return decompressed.ToArray();
    }

    private static IList<byte> EncodeReferenceInt(int value, int width)
    {
        if (!((value >= 0) && (value < (Math.Pow(_referenceIntBase, width) - 1))))
        {
            throw new ArgumentException(string.Format("Reference int out of range: {0} (width = {1})", value, width));
        }

        IList<byte> encoded = new List<byte>();

        while (value > 0)
        {
            encoded.Insert(0, (byte)((value % _referenceIntBase) + _referenceIntFloorCode));
            value = (int)Math.Floor((double)value / _referenceIntBase);
        }

        int missingLength = width - encoded.Count;

        for (int i = 0; i < missingLength; i++)
        {
            encoded.Insert(0, (byte)ReferenceIntFloorCode);
        }

        return encoded;
    }

    private static IList<byte> EncodeReferenceLength(int length)
    {
        return EncodeReferenceInt(length - _minStringLength, 1);
    }

    private static int DecodeReferenceInt(IList<byte> data, int width)
    {
        int value = 0, charCode;

        for (int i = 0; i < width; i++)
        {
            value *= _referenceIntBase;
            charCode = data[i];

            if (!((charCode >= _referenceIntFloorCode) && (charCode <= ReferenceIntCeilCode)))
            {
                throw new ArgumentException("Invalid char code in reference int: " + charCode);
            }

            value += charCode - _referenceIntFloorCode;
        }

        return value;
    }

    private static int DecodeReferenceLength(IList<byte> data)
    {
        return DecodeReferenceInt(data, 1) + _minStringLength;
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ The lazy IEnumerable is a good idea, I'll try it later \$\endgroup\$
    – Phate01
    Nov 4, 2015 at 14:24
  • \$\begingroup\$ @Phate01, I added other suggestions in addition to the previous ones. Let me know if something's unclear. \$\endgroup\$ Nov 4, 2015 at 16:08
  • \$\begingroup\$ The stopwatch and the console.writeline are just for debugging. Some of those fields are meant to be private, as they were in the java version of this class. As for the static context I think that it has to be an object, but with some factory methods which will be the only ones available on the outside. This is a widely-used practice. I'm implementing your changes, I'll stay in touch. \$\endgroup\$
    – Phate01
    Nov 5, 2015 at 13:29
  • \$\begingroup\$ I did as you suggested but there haven't been significant performance improvements. \$\endgroup\$
    – Phate01
    Nov 5, 2015 at 13:36
  • \$\begingroup\$ Ok, I'll take a more in-depth look at it later today \$\endgroup\$ Nov 5, 2015 at 13:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.