Tags: , , , , , | Categories: General Posted by nurih on 3/6/2009 1:59 AM | Comments (0)

So I wrote the thing up. Compared with a fairly common alternative, a run of the more type safe and render friendly template took 260 ms to merge 10k iterations vs. 480 ms for the alternative. In addition, the alternative went through almost twice the allocations (meaning more GC would result).

I'm kind of pleased that this is faster than the alternative, but am going to do some more thinking of a better way.  A 46% reduction is nice, but it's just under twice as fast. Still, the benefits of a cached template and token/key checking beats the alternative.

Toying with the idea of a strongly typed template leads to a dead end pretty much. Sure, one could dynamically emit a class that exposes the token names as properties, but then how would you bind to these in compile time? since the tokens are NOT known at compile time, the programmer using the class would have to populate a name value pair in some fashion, so that at run time the binding could be made.

 

//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IFasterTemplate
    {
        string ID { get; }
        void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues);
        IRuntimeValues GetRuntimeValuesPrototype();
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IRuntimeValues
    {
        bool AddValue(string tokenName, string value);
        bool AddValues(IEnumerable values);
        string[] GetAllowedValues();
        IRuntimeValues GetCopy();
        bool IsAlowed(string tokenName);
        string this[string tokenName] { get; }
        string ID { get; }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public static class FasterTempateFactory
    {
        public static IFasterTemplate GetFasterTemplate(TokenConfiguration config, ref string template)
        {
            string id = Guid.NewGuid().ToString();
            TemplateParts templateParts = processTemplateParts(id, config, ref template);
            IRuntimeValues runtimeValuesPrototype = processTokens(id, templateParts);
            IFasterTemplate result = new FasterTemplate(id, templateParts, runtimeValuesPrototype);
            return result;
        }
        private static IRuntimeValues processTokens(string id, TemplateParts templateParts)
        {
            RuntimeValues result = new RuntimeValues(id);
            for (int i = 0; i < templateParts.Count; i++)
                if (templateParts[i].IsToken)
                    result[templateParts[i].Value] = string.Empty;
            return result;
        }
        private static TemplateParts processTemplateParts(string id, TokenConfiguration config, ref string template)
        {
            TemplateParts result = new TemplateParts();
            char currentChar;
            bool isToken = false;
            StringBuilder sbText = new StringBuilder(template.Length / 2);
            StringBuilder sbToken = new StringBuilder(64);
            for (int idx = 0; idx < template.Length; idx++)
            {
                currentChar = template[idx];
                if (currentChar == config.TokenStartMarker)
                { isToken = true; result.Add(new TemplatePart(sbText.ToString(), false)); sbText.Length = 0; }
                else if (currentChar == config.TokenEndMarker) { isToken = false; result.Add(new TemplatePart(sbToken.ToString(), true)); sbToken.Length = 0; } else { if (isToken)                        sbToken.Append(currentChar); else                        sbText.Append(currentChar); }
            } if (isToken == true) throw new ArgumentException("Template has unclosed token marker"); if (sbText.Length > 0) result.Add(new TemplatePart(sbText.ToString(), false)); return result;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class FasterTemplate : IFasterTemplate
    {
        private string _ID;
        private TemplateParts _TemplateParts;
        private IRuntimeValues _RuntimeValuesPrototype;
        internal FasterTemplate(string ID, TemplateParts templateParts, IRuntimeValues runtimeValuesPrototype) { _ID = ID; _TemplateParts = templateParts; _RuntimeValuesPrototype = runtimeValuesPrototype; } public IRuntimeValues GetRuntimeValuesPrototype() { return _RuntimeValuesPrototype.GetCopy(); } public string ID { get { return _ID; } }
        public void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues)
        {
            if (runtimeValues.ID != this._ID) throw new ArgumentException("The runtime values supplied are not compatible with this template! Ensure you got the runtime values object from the template with ID " + this._ID); for (int i = 0, count = _TemplateParts.Count; i < count; i++)
            {
                TemplatePart part = _TemplateParts[i]; if (part.IsToken) { writer.Write(runtimeValues[part.Value]); }
                else
                {
                    writer.Write(part.Value);
                }
            }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class RuntimeValues : Nuri.FasterTemplate.IRuntimeValues
    {
        private Dictionary<string,string> _AllowedValues;
        internal string _ID;
        internal RuntimeValues(string ID, int capacity)
        {
            _AllowedValues = new Dictionary<string, string>(capacity);
            _ID = ID;
        }
        internal RuntimeValues(string ID) : this(ID, 0x10) { }
        internal Dictionary<string,string> AllowedValues
        {
            get { return _AllowedValues; }
        }
        public string[] GetAllowedValues()
        {
            string[] result = new string[_AllowedValues.Count];
            int i = 0;
            foreach (string key in _AllowedValues.Keys)
            { result[i++] = key; }
            return result;
        }
        public bool IsAlowed(string tokenName)
        {
            return _AllowedValues.ContainsKey(tokenName);
        }
        public bool AddValue(string tokenName, string value)
        {
            if (_AllowedValues.ContainsKey(tokenName))
            {
                _AllowedValues[tokenName] = value;
                return true;
            }
            else
                return false;
        }

        public bool AddValues(IEnumerable values)
        {
            bool result = true;
            foreach (KeyValuePair<string, string> pair in values)
            {
                result = result && this.AddValue(pair.Key, pair.Value);
            }
            return result;
        }
        public string this[string tokenName]
        {
            get
            {
                return _AllowedValues[tokenName];
            }
            internal set
            { _AllowedValues[tokenName] = value; }
        }
        public IRuntimeValues GetCopy()
        {
            RuntimeValues result = new RuntimeValues(this._ID, _AllowedValues.Count);
            foreach (string key in _AllowedValues.Keys)
                result.AllowedValues[key] = string.Empty;

            return result;
        }
        public string ID
        {
            get { return _ID; }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal struct TemplatePart
    {
        public bool IsToken;
        public string Value;
        public TemplatePart(string value, bool isToken)
        {
            this.Value = value;
            this.IsToken = isToken;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    class TemplateParts : List<TemplatePart> { }
}
//------------------------------------ 
namespace Nuri.FasterTemplate
{
    public struct TokenConfiguration
    {
        public readonly char TokenStartMarker;
        public readonly char TokenEndMarker;
        public TokenConfiguration(char tokenStartMarker, char tokenEndMarker)
        {
            TokenStartMarker = tokenStartMarker;
            TokenEndMarker = tokenEndMarker;
        }
    }
}
Tags: , , | Categories: Code Development, Performance Posted by nurih on 11/8/2008 1:26 PM | Comments (0)
Announcing a newly added a Codeplex project "Denum" code generator.

The Denum is a class / pattern for representing fairly static metadata from a database in an in-memory structure.
The structure behaves much like an Enum, but contains static members for each data member so that compile time type checking helps in transparency and coherency of your application logic and the build itself against the database version.
Tags: , , , , , | Categories: Code Development, General, Web, Performance Posted by nurih on 8/21/2006 4:36 AM | Comments (0)

So I wrote the thing up. Compared with a fairly common alternative, a run of the more type safe and render friendly template took 260 ms to merge 10k iterations vs. 480 ms for the alternative. In addition, the alternative went through almost twice the allocations (meaning more GC would result).

I'm kind of pleased that this is faster than the alternative, but am going to do some more thinking of a better way.  A 46% reduction is nice, but it's just under twice as fast. Still, the benefits of a cached template and token/key checking beats the alternative.

Toying with the idea of a strongly typed template leads to a dead end pretty much. Sure, one could dynamically emit a class that exposes the token names as properties, but then how would you bind to these in compile time? since the tokens are NOT known at compile time, the programmer using the class would have to populate a name value pair in some fashion, so that at run time the binding could be made.

//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IFasterTemplate
    {
        string ID { get; }
        void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues);
        IRuntimeValues GetRuntimeValuesPrototype();
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public interface IRuntimeValues
    {
        bool AddValue(string tokenName, string value);
        bool AddValues(IEnumerable values);
        string[] GetAllowedValues();
        IRuntimeValues GetCopy();
        bool IsAlowed(string tokenName);
        string this[string tokenName] { get; }
        string ID { get; }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    public static class FasterTempateFactory
    {
        public static IFasterTemplate GetFasterTemplate(TokenConfiguration config, ref string template)
        {
            string id = Guid.NewGuid().ToString();
            TemplateParts templateParts = processTemplateParts(id, config, ref template);
            IRuntimeValues runtimeValuesPrototype = processTokens(id, templateParts);
            IFasterTemplate result = new FasterTemplate(id, templateParts, runtimeValuesPrototype);
            return result;
        }
        private static IRuntimeValues processTokens(string id, TemplateParts templateParts)
        {
            RuntimeValues result = new RuntimeValues(id);
            for (int i = 0; i < templateParts.Count; i++)
                if (templateParts[i].IsToken)
                    result[templateParts[i].Value] = string.Empty;
            return result;
        }
        private static TemplateParts processTemplateParts(string id, TokenConfiguration config, ref string template)
        {
            TemplateParts result = new TemplateParts();
            char currentChar;
            bool isToken = false;
            StringBuilder sbText = new StringBuilder(template.Length / 2);
            StringBuilder sbToken = new StringBuilder(64);
            for (int idx = 0; idx < template.Length; idx++)
            {
                currentChar = template[idx];
                if (currentChar == config.TokenStartMarker)
                { isToken = true; result.Add(new TemplatePart(sbText.ToString(), false)); sbText.Length = 0; }
                else if (currentChar == config.TokenEndMarker) { isToken = false; result.Add(new TemplatePart(sbToken.ToString(), true)); sbToken.Length = 0; } else { if (isToken)                        sbToken.Append(currentChar); else                        sbText.Append(currentChar); }
            } if (isToken == true) throw new ArgumentException("Template has unclosed token marker"); if (sbText.Length > 0) result.Add(new TemplatePart(sbText.ToString(), false)); return result;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class FasterTemplate : IFasterTemplate
    {
        private string _ID;
        private TemplateParts _TemplateParts;
        private IRuntimeValues _RuntimeValuesPrototype;
        internal FasterTemplate(string ID, TemplateParts templateParts, IRuntimeValues runtimeValuesPrototype) { _ID = ID; _TemplateParts = templateParts; _RuntimeValuesPrototype = runtimeValuesPrototype; } public IRuntimeValues GetRuntimeValuesPrototype() { return _RuntimeValuesPrototype.GetCopy(); } public string ID { get { return _ID; } }
        public void Render(System.IO.TextWriter writer, IRuntimeValues runtimeValues)
        {
            if (runtimeValues.ID != this._ID) throw new ArgumentException("The runtime values supplied are not compatible with this template! Ensure you got the runtime values object from the template with ID " + this._ID); for (int i = 0, count = _TemplateParts.Count; i < count; i++)
            {
                TemplatePart part = _TemplateParts[i]; if (part.IsToken) { writer.Write(runtimeValues[part.Value]); }
                else
                {
                    writer.Write(part.Value);
                }
            }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal class RuntimeValues : Nuri.FasterTemplate.IRuntimeValues
    {
        private Dictionary<string,string> _AllowedValues;
        internal string _ID;
        internal RuntimeValues(string ID, int capacity)
        {
            _AllowedValues = new Dictionary<string, string>(capacity);
            _ID = ID;
        }
        internal RuntimeValues(string ID) : this(ID, 0x10) { }
        internal Dictionary<string,string> AllowedValues
        {
            get { return _AllowedValues; }
        }
        public string[] GetAllowedValues()
        {
            string[] result = new string[_AllowedValues.Count];
            int i = 0;
            foreach (string key in _AllowedValues.Keys)
            { result[i++] = key; }
            return result;
        }
        public bool IsAlowed(string tokenName)
        {
            return _AllowedValues.ContainsKey(tokenName);
        }
        public bool AddValue(string tokenName, string value)
        {
            if (_AllowedValues.ContainsKey(tokenName))
            {
                _AllowedValues[tokenName] = value;
                return true;
            }
            else
                return false;
        }

        public bool AddValues(IEnumerable values)
        {
            bool result = true;
            foreach (KeyValuePair<string, string> pair in values)
            {
                result = result && this.AddValue(pair.Key, pair.Value);
            }
            return result;
        }
        public string this[string tokenName]
        {
            get
            {
                return _AllowedValues[tokenName];
            }
            internal set
            { _AllowedValues[tokenName] = value; }
        }
        public IRuntimeValues GetCopy()
        {
            RuntimeValues result = new RuntimeValues(this._ID, _AllowedValues.Count);
            foreach (string key in _AllowedValues.Keys)
                result.AllowedValues[key] = string.Empty;

            return result;
        }
        public string ID
        {
            get { return _ID; }
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    internal struct TemplatePart
    {
        public bool IsToken;
        public string Value;
        public TemplatePart(string value, bool isToken)
        {
            this.Value = value;
            this.IsToken = isToken;
        }
    }
}
//------------------------------------
namespace Nuri.FasterTemplate
{
    class TemplateParts : List<TemplatePart> { }
}
//------------------------------------ 
namespace Nuri.FasterTemplate
{
    public struct TokenConfiguration
    {
        public readonly char TokenStartMarker;
        public readonly char TokenEndMarker;
        public TokenConfiguration(char tokenStartMarker, char tokenEndMarker)
        {
            TokenStartMarker = tokenStartMarker;
            TokenEndMarker = tokenEndMarker;
        }
    }
}
Tags: , , , | Categories: Code Development, General, Performance Posted by nurih on 8/18/2006 8:40 AM | Comments (0)

I'm toying with a class that will allow for efficient token replacement at runtime.

Why re-invent the wheel?

  1. Its fun
  2. A bare class that does just this is rare
  3. Absent a class like this, people just use all kinds of string manipulation techniques that can be slow and sluggish.

What am I out to solve? Let's say your website supports some notion of a CMS. So you let some users publish pages / content by filling in a form and storing the content in the db. Next usually comes the request that special variables can be planted in the content. This includes ads, formatting, dynamic links, personalization data etc. The first reflex is to just use string replacement. But if the content is not the whole page, the tendency is to assign the string to a literal or somehow just create a string field in your page and then assign it to some code in front control. The issue with this is that at the very best, the input string (template) is scanned every time in order to determine where the token markers are. At the very worst this is combined with string thrashing and ends up being a wasteful string concatenation exercise. Me thinks a factory class TemplateFactory can generate an immutable template class. That class can be cached for a long time. The template innards would all be private and hidden. They would consist of all the static parts and all the "holes". The template will be in itself a factory, letting a user get an instance of a RuntimeValue object. The runtime value object will support a .Add(TokenName, TokenValue) method, which would check the token names and be bound to the originating factory (the template that created it). This way, you get some runtime checking of invalid token assignments. The template would expose a method Render(writer, runtimeValues) . The render method would be the one merging the template with the runtime values. Notably, there would NOT be a string MergeValues(values) call. That's because strings are immutable and storing the merged string at any scope is a waste. The only reason one would need to store it is for later use in a stream context (IIS response stream, mail message, file etc). If this pains anyone, the use of StringWriter is recommended. But the intended use of this scheme is to plant the Render() call somewhere in the OnRender() event equivalent of your application. The idea is that the object would both enforce a correct (IMHO) deferral of merging to the presentation time, and implement an efficient algorithm for fast merges. The instance creation is going to bear the grunt of parsing the template and scanning for token markers. If a CMS system is the target app, it can pre create these runtime objects and keep them in memory. A template object would specifically NOT be created each time you need to merge. Only the lightweight RuntimeValues need to be gotten from a factory and populated each time, because these are the real runtime instance variants. The rest of the template is "static".