My Calendar generation code is slow. How do I improve it? I have a model I have created to generate iCal and the necessary JSON for the jQuery calendar plugin Full Calendar to work. When I display the page, the JSON generation takes about 7 seconds. The query itself has been running in only a few microseconds. So I know my code is the problem. 

I was trying to get the format like what Full Calendar expects thus why I wrote the code how I did. My guess is that the below is the problem. Any thoughts on where I can improve this?

#region Event
public class Event
{
    public string Title { set; get; }
    public string Description { set; get; }
    public string URL { set; get; }
    public string UID { set; get; }
    public DateTime DateTimeStamp { set; get; }
    public DateTime CreatedDateTime { set; get; }
    public DateTime Start { set; get; }
    public DateTime End { set; get; }
    public bool AllDay { set; get; }
}
#endregion

public class Calendar
{
    #region Model
    public string CalendarName { set; get; }
    public string Product { set; get; }
    public string TimeZone { set; get; }
    public List&lt;Event&gt; Events { private set; get; }
    #endregion

    /// &lt;summary&gt;
    /// Creates a calendar json string.
    /// &lt;/summary&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public string ToFullCalendarJsonString()
    {
        StringBuilder sb = new StringBuilder();

        sb.Append("[");

        int count = Events.Count;
        int i = 0;

        foreach (Event eventItem in Events)
        {
            sb.Append("{");
            sb.AppendFormat("\"title\":\"{0}\",", eventItem.Title);
            sb.AppendFormat("\"allDay\":\"{0}\",", eventItem.AllDay);

            if (eventItem.AllDay)
            {
                sb.AppendFormat("\"start\":\"{0}T00:00:00\",", eventItem.Start.ToString("yyyy-MM-dd"));
                sb.AppendFormat("\"end\":\"{0}T00:00:00\",", eventItem.End.ToString("yyyy-MM-dd"));
            }
            else
            {
                sb.AppendFormat("\"start\":\"{0}T{1}\",", eventItem.Start.ToString("yyyy-MM-dd"), eventItem.Start.ToString("HH:mm:ss"));
                sb.AppendFormat("\"end\":\"{0}T{1}\",", eventItem.End.ToString("yyyy-MM-dd"), eventItem.End.ToString("HH:mm:ss"));
            }
            sb.AppendFormat("\"url\":\"{0}\"", eventItem.URL);
            sb.Append("}");

            i++;

            if (i &lt; count)
            {
                sb.Append(",");
            }
        }

        sb.Append("]");

        return sb.ToString();
    }
I just read somewhere that AppendFormat() can be slower than simple Append() calls. Being shocked by reading this, I decided to investigate.

For 1,000,000 empty events the times required are:


With AppendFormat: 9297 ticks
Without AppendFormat: 8268 ticks


That is a considerable difference of 11%!

I'm guessing this is due to the lookup of the arguments and such. It would be nice if AppendFormat() would be recompiled to Append() calls only by default.

This is the code:

    /// &lt;summary&gt;
    /// Creates a calendar json string.
    /// &lt;/summary&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public string ToFullCalendarJsonStringFaster()
    {
        StringBuilder sb = new StringBuilder();

        sb.Append( "[" );

        int count = Events.Count;
        int i = 0;

        foreach ( Event eventItem in Events )
        {
            sb.Append( "{" );

            sb.Append("\"title\":\"");
            sb.Append(eventItem.Title);
            sb.Append("\",");
            sb.Append("\"allDay\":\"");
            sb.Append(eventItem.AllDay);
            sb.Append("\",");

            if ( eventItem.AllDay )
            {
                // My test never comes here, so I left it out.
            }
            else
            {
                sb.Append("\"start\":\"");
                sb.Append(eventItem.Start.ToString("yyyy-MM-dd"));
                sb.Append("T");
                sb.Append(eventItem.Start.ToString("HH:mm:ss"));
                sb.Append("\",");

                sb.Append("\"end\":\"");
                sb.Append(eventItem.End.ToString("yyyy-MM-dd"));
                sb.Append("T");
                sb.Append(eventItem.End.ToString("HH:mm:ss"));
                sb.Append("\",");
            }
            sb.Append("\"url\":\"");
            sb.Append(eventItem.URL);
            sb.Append("\"");
            sb.Append( "}" );

            i++;

            if ( i &lt; count )
            {
                sb.Append( "," );
            }
        }

        sb.Append( "]" );

        return sb.ToString();
    }


By also applying Snowbear's earlier replies:


Using yyyy-MM-ddTHH:mm:ss as format strings, gives an extra speed difference of 5%
Preallocating StringBuilder size, gives an extra speed difference of 6%


In total, after applying all changes, the code is 22% faster. :)

Bottomline is, there probably isn't a 'magic' solution which can make it go instant with so many events, but you can improve the speed considerably. I suggest you run the processing on a BackgroundWorker.



... this is getting even more ridiculous. Changing the date formatting to the following:

//sb.Append(eventItem.Start.ToString( "yyyy-MM-ddTHH:mm:ss" ) );
sb.Append(eventItem.Start.Year);
sb.Append("-");
sb.Append(eventItem.Start.Month);
sb.Append("-");
sb.Append(eventItem.Start.Day);
sb.Append("T");
sb.Append(eventItem.Start.Hour);
sb.Append(":");
sb.Append(eventItem.Start.Minute);
sb.Append(":");
sb.Append(eventItem.Start.Second);
sb.Append("\",");


... gives another speed increase and makes it 34% faster in total. Might slow down 
again if you need more specific formatting.



Beware: this last update is probably erronous. I asked a question about proper usage of PLINQ.

I haven't used Parallel LINQ (PLINQ) yet. But this seemed a nice use for it. After replacing the for by:

Events.AsParallel().AsOrdered().ForAll( eventItem =&gt;
{
    ...
} );    


I get a total speed increase of 43%, again 9% faster. :) This is on a dual core processor. PC's with more cores should perform better. I don't know how PLINQ works exactly, but I would think it could work even faster if one iteration doesn't need to wait on another. The StringBuilder and i are exposed as closures. Anyone got any better approaches than ForAll()?