Showing posts with label c# optimization. Show all posts
Showing posts with label c# optimization. Show all posts

Thursday, September 16, 2010

C# XmlWriter vs XmlDocument vs StreamWriter

Use XmlWriter for creating xml document.The below example uses the following classes
  • Contact class is used for storing name and address
  • AddressBook is the colleciton of Contact class
The c# code example in this article will generate xml document as below, but with 10000 contacts elements.
<AddressBook>
  <Contact>
    <Name>Person1</Name>
    <Address1><![CDATA[Number101]]></Address1>
    <Address2><![CDATA[Address501]]></Address2>
    <Zip>50001</Zip>
    <Phone><![CDATA[999991]]></Phone>
 </Contact>
</AddressBook>
The example first creates and populates an AddressBook class with 10000 Contact objects and measures time taken to generate the xml file with 10000 contacts using using XmlDocument, XmlWriter and StreamWriter (usign StringBuilder).
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Xml.Linq;
namespace XmlWrite
{
    class Contact
    {
        public string Name { get; set; }
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public int Zip { get; set; }
        public string Phone { get; set; }
        public Contact(string strName, string strAddress1, string strAddress2,
            int iZip,
            string strPhone)
        {
            Name = strName;
            Address1 = strAddress1;
            Address2 = strAddress2;
            Zip = iZip;
            Phone = strPhone;
        }
    }
    class AddressBook
    {
        private List<Contact> InnerList = null;
        public List<Contact> GetContacts()
        {
            return InnerList;
        }
        public AddressBook()
        {
            InnerList = new List<Contact>();
        }
        public void Add(Contact objContact)
        {
            InnerList.Add(objContact);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objSW = null;
            XmlDocument objXmlDoc = null;
            XmlWriter objXmlWriter = null;
            XDocument objXDoc = null;
            StringBuilder objSB = null;
            StreamWriter objStreamWriter = null;
            AddressBook objAddrBook = null;
            int iCount = 10000;
            List<Contact> objContacts = null;

            //populate contacts in the addressbook object model
            objAddrBook = new AddressBook();
            objSW = new Stopwatch();
            for (int i = 0; i < iCount; i++)
            {
                Contact objContact = null;
                string strName = "Person" + (i + 1);
                string strAddress1 = "Number" + (i + 101);
                string strAddress2 = "Address" + (i + 501);
                int iZip = 50000 + (i + 1);
                string strPhone = "99999" + (i + 1);
                objContact = new Contact(strName, strAddress1, strAddress2, iZip,
                    strPhone);
                objAddrBook.Add(objContact);
            }
            //xml creation using XmlDocument
            objSW.Reset();
            objSW.Start();
            objXmlDoc = new XmlDocument();
            XmlElement objXmlAddrBook = objXmlDoc.CreateElement("AddressBook");
            objContacts = objAddrBook.GetContacts();
            for (int i = 0; i < objContacts.Count; i++)
            {
                XmlElement objXmlContact = objXmlDoc.CreateElement("Contact");
                XmlElement objXmlName = objXmlDoc.CreateElement("Name");
                objXmlName.InnerText = objContacts[i].Name;
                objXmlContact.AppendChild(objXmlName);
                XmlElement objXmlAddress1 = objXmlDoc.CreateElement("Address1");
                objXmlAddress1.AppendChild(
                    objXmlDoc.CreateCDataSection(objContacts[i].Address1));
                objXmlContact.AppendChild(objXmlAddress1);
                XmlElement objXmlAddress2 = objXmlDoc.CreateElement("Address2");
                objXmlAddress2.AppendChild(
                    objXmlDoc.CreateCDataSection(objContacts[i].Address2));
                objXmlContact.AppendChild(objXmlAddress2);
                XmlElement objXmlZip = objXmlDoc.CreateElement("Zip");
                objXmlZip.InnerText = objContacts[i].Zip.ToString();
                objXmlContact.AppendChild(objXmlZip);
                XmlElement objXmlPhone = objXmlDoc.CreateElement("Phone");
                objXmlPhone.AppendChild(
                    objXmlDoc.CreateCDataSection(objContacts[i].Phone));
                objXmlContact.AppendChild(objXmlPhone);
                objXmlAddrBook.AppendChild(objXmlContact);
            }
            objXmlDoc.AppendChild(objXmlAddrBook);
            objXmlDoc.Save("address_dom.xml");
            objSW.Stop();
            Console.WriteLine("XML file created using XmlDocument");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            //xml creation using XmlWriter
            objSW.Reset();
            objSW.Start();
            XmlWriterSettings objSettings = new XmlWriterSettings();
            objSettings.Indent = true;
            using (objXmlWriter = XmlWriter.Create("address_xw.xml", objSettings))
            {
                objXmlWriter.WriteStartElement("AddressBook");
                objContacts = objAddrBook.GetContacts();
                for (int i = 0; i < objContacts.Count; i++)
                {
                    objXmlWriter.WriteStartElement("Contact");

                    objXmlWriter.WriteStartElement("Name");
                    objXmlWriter.WriteString(objContacts[i].Name);
                    objXmlWriter.WriteEndElement();
                    objXmlWriter.WriteStartElement("Address1");
                    objXmlWriter.WriteCData(objContacts[i].Address1);
                    objXmlWriter.WriteEndElement();
                    objXmlWriter.WriteStartElement("Address2");
                    objXmlWriter.WriteCData(objContacts[i].Address2);
                    objXmlWriter.WriteEndElement();
                    objXmlWriter.WriteStartElement("Zip");
                    objXmlWriter.WriteString(objContacts[i].Zip.ToString());
                    objXmlWriter.WriteEndElement();
                    objXmlWriter.WriteStartElement("Phone");
                    objXmlWriter.WriteCData(objContacts[i].Phone);
                    objXmlWriter.WriteEndElement();

                    objXmlWriter.WriteEndElement();
                }
                objXmlWriter.WriteEndElement();
            }
            objSW.Stop();
            Console.WriteLine("XML file created using XmlWriter");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            //xml creation using StringBuilder
            objSW.Reset();
            objSW.Start();
            objSB = new StringBuilder();
            objSB.AppendLine("<AddressBook>");
            objContacts = objAddrBook.GetContacts();

            for (int i = 0; i < objContacts.Count; i++)
            {
                objSB.AppendLine("<Contact>");
                objSB.AppendLine("<Name>" + objContacts[i].Name + "</Name>");
                objSB.AppendLine(
                "<Address1><![CDATA[" + objContacts[i].Address1 + "]]></Address1>");
                objSB.AppendLine(
                "<Address2><![CDATA[" + objContacts[i].Address2 + "]]></Address2>");
                objSB.AppendLine("<Zip>" + objContacts[i].Zip + "</Zip>");
                objSB.AppendLine(
                "<Phone><![CDATA[" + objContacts[i].Phone + "]]></Phone>");
                objSB.AppendLine("</Contact>");
            }
            objSB.AppendLine("</AddressBook>");
            using (objStreamWriter = new StreamWriter("address_sb.xml", false))
            {
                objStreamWriter.Write(objSB.ToString());
            }
            objSW.Stop();
            Console.WriteLine("XML file created using StringBuilder");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            //xml creation using XDocument (LINQ)
            objSW.Reset();
            objSW.Start();
            objContacts = objAddrBook.GetContacts();
            XElement[] arrXElem = new XElement[objContacts.Count];
            for (int i = 0; i < objContacts.Count; i++)
            {
                arrXElem[i] = new XElement("Contact",
                    new XElement("Name", objContacts[i].Name),
                    new XElement("Address1",
                    new XCData(objContacts[i].Address1)),
                    new XElement("Address2",
                    new XCData(objContacts[i].Address2)),
                    new XElement("Zip", objContacts[i].Zip),
                    new XElement("Phone", new XCData(objContacts[i].Phone)));
            }
            objXDoc = new XDocument(new XElement("AddressBook",arrXElem));
            objXDoc.Save("address_xdocument.xml");
            objSW.Stop();
            Console.WriteLine("XML file created using XDocument");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            Console.ReadLine();

        }
    }
}
Output:
XML file created using XmlDocument
TimeElapsed (Stopwatch float):90.0323ms
TimeElapsed (Stopwatch rounded):90ms
XML file created using XmlWriter
TimeElapsed (Stopwatch float):36.2555ms
TimeElapsed (Stopwatch rounded):36ms
XML file created using StringBuilder
TimeElapsed (Stopwatch float):91.5911ms
TimeElapsed (Stopwatch rounded):91ms
XML file created using XDocument
TimeElapsed (Stopwatch float):71.3794ms
TimeElapsed (Stopwatch rounded):71ms

Note: The StringBuilder performance can be improved by specifying the required capacity when creating the StringBuilder object.

Note: XmlDocument can be considered, if forward and backward reference is unavoidable during the generation of xml document.

Rule: Use XmlWriter for generating or creating xml document.

C# Structure vs Class

Structure performs better than the reference type (Class) with respect to CPU usage and memory usage. Consider using structure over class,
  • If your application has to support data which does not require inheritance and interface
  • The data is in large number
  • The data is only of primitive data type like integer, byte, float, etc.
  • Probability a graphics application or application which deals with large data

Note: Structure with large number of members will degrade the performance when passed as parameter/argument to functions because the values of the entire structure is copied as parameter/argument to the function.

Below code measures the creation of 100000 objects of a class and also measures the time taken to search an object from the collection.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Drawing;
namespace ClassTimeElapsed
{
    class Brick
    {
        public int Id;
        public float X;
        public float Y;
        public float Width;
        public float Height;
        public Color Color;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objStopwatch;
            Brick[] arrBrick;
            int iCount;
            iCount = 100000;

            objStopwatch = new Stopwatch();
            objStopwatch.Reset();
            objStopwatch.Start();
            arrBrick = new Brick[iCount];

            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i] = new Brick();
            }
            int iConst = 15;
            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i].Id = i + 1;
                arrBrick[i].X = i * iConst;
                arrBrick[i].Y = 0;
                arrBrick[i].Width = 10;
                arrBrick[i].Height = 10;
                arrBrick[i].Color = Color.Teal;
            }
            objStopwatch.Stop();
            Console.WriteLine(
                "Class time elapsed for allocation and population");
            //timeelapsed
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds);
            int iSearchId = 99880;
            Brick objBrick = null;
            objStopwatch.Reset();
            objStopwatch.Start();
            for (int i = 0; i < arrBrick.Length; i++)
            {
                if (iSearchId == arrBrick[i].Id)
                {
                    objBrick = arrBrick[i];
                    break;
                }
            }
            objStopwatch.Stop();
            Console.WriteLine(
                "Class time elapsed for searching");
            //search result
            Console.WriteLine(
                "search result: Id:{0},X:{1},Y:{2},Width:{3},Height:{4},Color:{5}",
                objBrick.Id, objBrick.X, objBrick.Y, objBrick.Width, objBrick.Height,
                objBrick.Color);
            //timeelapsed
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Output:
Class time elapsed for allocation and population
TimeElapsed (Stopwatch float):13.7327ms
TimeElapsed (Stopwatch rounded):13ms
Class time elapsed for searching
search result: Id:99880,X:1498185,Y:0,Width:10,Height:10,Color:Color [Teal]
TimeElapsed (Stopwatch float):1.3015ms
TimeElapsed (Stopwatch rounded):1ms
Below code measures the creation of 100000 objects of a struct and also measures the time taken to search an object from the collection.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Drawing;
namespace StructTimeElapsed
{
    struct Brick
    {
        public int Id;
        public float X;
        public float Y;
        public float Width;
        public float Height;
        public Color Color;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objStopwatch;
            Brick[] arrBrick;
            int iCount;
            iCount = 100000;
            objStopwatch = new Stopwatch();
            objStopwatch.Reset();
            objStopwatch.Start();
            arrBrick = new Brick[iCount];
            int iConst = 15;
            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i].Id = i + 1;
                arrBrick[i].X = i * iConst;
                arrBrick[i].Y = 0;
                arrBrick[i].Width = 10;
                arrBrick[i].Height = 10;
                arrBrick[i].Color = Color.Teal;

            }
            objStopwatch.Stop();
            Console.WriteLine(
                "Struct time elapsed for allocation and population");
            //timeelapsed
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds);
            int iSearchId = 99880;
            Brick objBrick;
            objBrick.Id = 0;
            objBrick.X = 0;
            objBrick.Y = 0;
            objBrick.Width = 0;
            objBrick.Height = 0;
            objBrick.Color = Color.Transparent;
            objStopwatch.Reset();
            objStopwatch.Start();
            for (int i = 0; i < arrBrick.Length; i++)
            {
                if (iSearchId == arrBrick[i].Id)
                {
                    objBrick = arrBrick[i];
                    break;
                }
            }
            objStopwatch.Stop();
            Console.WriteLine(
                "Struct time elapsed for searching");
            //search result
            Console.WriteLine(
                "search result: Id:{0},X:{1},Y:{2},Width:{3},Height:{4},Color:{5}",
                objBrick.Id, objBrick.X, objBrick.Y, objBrick.Width, objBrick.Height,
                objBrick.Color);
            //timeelapsed
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Output:
Struct time elapsed for allocation and population
TimeElapsed (Stopwatch float):3.0684ms
TimeElapsed (Stopwatch rounded):3ms
Struct time elapsed for searching
search result: Id:99880,X:1498185,Y:0,Width:10,Height:10,Color:Color [Teal]
TimeElapsed (Stopwatch float):1.0981ms
TimeElapsed (Stopwatch rounded):1ms
Below code measures the memory usage of creation of 100000 objects of a class.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Drawing;
namespace ClassMemoryUsage
{
    class Brick
    {
        public int Id;
        public float X;
        public float Y;
        public float Width;
        public float Height;
        public Color Color;
    }
    class Program
    {
        static void Main(string[] args)
        {
            long lngGCMemStart = 0;
            long lngGCMemEnd = 0;
            long lngProcessMemStart = 0;
            long lngProcessMemEnd = 0;
            Brick[] arrBrick;
            int iCount;
            iCount = 100000;
            Process objProcess = Process.GetCurrentProcess();
            //Measure starting point memory
            lngGCMemStart = System.GC.GetTotalMemory(true);
            lngProcessMemStart = objProcess.PrivateMemorySize64;
            arrBrick = new Brick[iCount];
            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i] = new Brick();
            }
            //Measure memory after allocating
            lngGCMemEnd = System.GC.GetTotalMemory(true);
            lngProcessMemEnd = objProcess.PrivateMemorySize64;
            int iConst = 15;
            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i].Id = i+1;
                arrBrick[i].X = i*iConst;
                arrBrick[i].Y = 0;
                arrBrick[i].Width = 10;
                arrBrick[i].Height = 10;
                arrBrick[i].Color = Color.Teal;
            }
            Console.WriteLine("Class memory usage.");
            //memoryusage difference
            Console.WriteLine("GC Memory Use:{0} (bytes)",
                lngGCMemEnd - lngGCMemStart);
            Console.WriteLine("Process Memory Use:{0} (bytes)",
                lngProcessMemEnd - lngProcessMemStart);
            Console.WriteLine("GC Memory Start:{0} (bytes)",
                lngGCMemStart.ToString());
            Console.WriteLine("Process Memory Start:{0} (bytes)",
                lngProcessMemStart.ToString());
            Console.WriteLine("GC Memory End:{0} (bytes)",
                lngGCMemEnd.ToString());
            Console.WriteLine("Process Memory End:{0} (bytes)",
                lngProcessMemEnd.ToString());
            Console.ReadLine();
        }
    }
}
Output:
Class memory usage.
GC Memory Use:4802368 (bytes)
Process Memory Use:0 (bytes)
GC Memory Start:130648 (bytes)
Process Memory Start:9232384 (bytes)
GC Memory End:4933016 (bytes)
Process Memory End:9232384 (bytes)
Below code measures the memory usage of creation of 100000 objects of a struct.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Drawing;

namespace StructMemoryUsage
{
    struct Brick
    {
        public int Id;
        public float X;
        public float Y;
        public float Width;
        public float Height;
        public Color Color;
    }

    class Program
    {
        static void Main(string[] args)
        {
            long lngGCMemStart = 0;
            long lngGCMemEnd = 0;
            long lngProcessMemStart = 0;
            long lngProcessMemEnd = 0;
            Brick[] arrBrick;
            int iCount;
            iCount = 100000;
            Process objProcess = Process.GetCurrentProcess();
            //Measure starting point memory
            lngGCMemStart = System.GC.GetTotalMemory(true);
            lngProcessMemStart = objProcess.PrivateMemorySize64;
            arrBrick = new Brick[iCount];
            //Measure memory after allocating
            lngGCMemEnd = System.GC.GetTotalMemory(true);
            lngProcessMemEnd = objProcess.PrivateMemorySize64;
            int iConst = 15;
            for (int i = 0; i < iCount; i++)
            {
                arrBrick[i].Id = i + 1;
                arrBrick[i].X = i * iConst;
                arrBrick[i].Y = 0;
                arrBrick[i].Width = 10;
                arrBrick[i].Height = 10;
                arrBrick[i].Color = Color.Teal;
            }
            Console.WriteLine("Struct memory usage.");
            //memoryusage difference
            Console.WriteLine("GC Memory Use:{0} (bytes)",
                lngGCMemEnd - lngGCMemStart);
            Console.WriteLine("Process Memory Use:{0} (bytes)",
                lngProcessMemEnd - lngProcessMemStart);
            Console.WriteLine("GC Memory Start:{0} (bytes)",
                lngGCMemStart.ToString());
            Console.WriteLine("Process Memory Start:{0} (bytes)",
                lngProcessMemStart.ToString());
            Console.WriteLine("GC Memory End:{0} (bytes)",
                lngGCMemEnd.ToString());
            Console.WriteLine("Process Memory End:{0} (bytes)",
                lngProcessMemEnd.ToString());
            Console.ReadLine();
        }
    }
}
Output:
Struct memory usage.
GC Memory Use:3602368 (bytes)
Process Memory Use:0 (bytes)
GC Memory Start:130656 (bytes)
Process Memory Start:9232384 (bytes)
GC Memory End:3733024 (bytes)
Process Memory End:9232384 (bytes)

Why? Structure is created in the stack.Structure is a value type.

Rule: Consider using structure for supporting large number of objects(data) in your application.

C# StringBuilder vs String Concatenation

Use StringBuilder for string concatenation. Concatenation of string using StringBuilder has better performance over string.
Below code measures the time taken to concatenate a string for 5000 iteration using “string” and “StringBuilder”.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace StringVsStringBuilder
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objStopwatch = new Stopwatch();
            string strValue=string.Empty;
            StringBuilder objSBValue = new StringBuilder();
            int iCount = 5000;
            objStopwatch.Reset();
            objStopwatch.Start();
            for (int i = 0; i < iCount; i++)
            {
                strValue = strValue+"simpleValue"+i.ToString();
            }
            objStopwatch.Stop();
            Console.WriteLine("concatenation using String.");
            Console.WriteLine(
                "TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds
                );
            Console.WriteLine(
                "TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds
                );
            //Console.WriteLine(strValue);
            objStopwatch.Reset();
            objStopwatch.Start();
            for (int i = 0; i < iCount; i++)
            {
                objSBValue.Append("simpleValue" + i.ToString());
            }
            objStopwatch.Stop();
            Console.WriteLine(
                "concatenation using StringBuilder."
                );
            Console.WriteLine(
                "TimeElapsed (Stopwatch float):{0}ms",
                objStopwatch.Elapsed.TotalMilliseconds
                );
            Console.WriteLine(
                "TimeElapsed (Stopwatch rounded):{0}ms",
                objStopwatch.ElapsedMilliseconds
                );
            //Console.WriteLine(objSBValue);
            Console.ReadLine();
        }
    }
}
Output:
concatenation using String.
TimeElapsed (Stopwatch float):316.6526ms
TimeElapsed (Stopwatch rounded):316ms
concatenation using StringBuilder.
TimeElapsed (Stopwatch float):2.4933ms
TimeElapsed (Stopwatch rounded):2ms

Why? Any object stored in heap is immutable with respect to size allocation, and any change to the underlying size will require reallocation. If the string is modified (concatenating another string onto it, changing its value), then a new string is created and this can have negative performance implications. The StringBuilder gets around the reallocation problem by using internal buffers. But once the buffer fills up, the reallocation occurs. It is recommended to specify the initial capacity to minimize the reallocation problem.

Rule: Use StringBuilder instead of string for string concatenation operation.

C# XmlReader vs XDocument vs XmlDocument

Use Xmlreader for parse xml document.
Create an xml file as show below, but with 10000 contact elements called as “address_xw_n.xml” and make it available in D driver (d:\).
<AddressBook>
  <Contact>
    <Name>Person1</Name>
    <Address1><![CDATA[Number101]]></Address1>
    <Address2><![CDATA[Address501]]></Address2>
    <Zip>50001</Zip>
    <Phone><![CDATA[999991]]></Phone>
 </Contact>
</AddressBook>
The below code demonstrate the time take to parse the above created xml file using XmlDocument, XDocument and XmlReader.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Xml.Linq;
namespace XmlParse
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objSW = null;
            String strNormalFilePath;
            strNormalFilePath = @"d:\address_xw_n.xml";
            XmlDocument xmlNDoc;
            XmlReader xmlNReader = null;
            XDocument xNDoc=null;
            objSW = new Stopwatch();

            //xml parsing using XmlDocument - Normal format
            objSW.Reset();
            objSW.Start();
            xmlNDoc = new XmlDocument();
            xmlNDoc.Load(strNormalFilePath);
            XmlElement xmlDocNElem = xmlNDoc.DocumentElement;
            for (int i = 0; i < xmlDocNElem.ChildNodes.Count; i++)
            {
                XmlNode xmlNode = xmlDocNElem.ChildNodes[i];
                for (int j = 0; j < xmlNode.ChildNodes.Count; j++)
                {
                    string strName = xmlNode.ChildNodes[j].Name;
                    string strValue = xmlNode.ChildNodes[j].ChildNodes[0].Value;
                    //Console.WriteLine(strName + "-" + strValue);
                }
            }
            //xmlNDoc = null;
            objSW.Stop();
            Console.WriteLine("XML parsing using XmlDocument - Normal format");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            //xml parsing using XDocument - Normal format
            objSW.Reset();
            objSW.Start();
            xNDoc = XDocument.Parse(xmlNDoc.OuterXml);
            foreach (XElement e in xNDoc.Root.Elements())
            {
                foreach (XElement c in e.Elements())
                {
                    string strName = c.Name.ToString();
                    string strValue = c.Value;
                    //Console.WriteLine(strName+"-"+strValue);
                }
            }
            xNDoc = null;
            objSW.Stop();
            Console.WriteLine("XML parsing using XDocument - Normal format");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            //xml parsing using XmlReader - Normal format
            objSW.Reset();
            objSW.Start();
            using (xmlNReader = XmlReader.Create(strNormalFilePath))
            {
                while (xmlNReader.Read())
                {
                    switch (xmlNReader.NodeType)
                    {
                        case XmlNodeType.Element:
                            {
                                switch (xmlNReader.Name)
                                {
                                    case "Name":
                                    case "Zip":
                                        {
                                            string strName = xmlNReader.Name;
                                            xmlNReader.Read();
                                            string strValue = xmlNReader.Value;
                                            //Console.WriteLine(strName +
                                            //  "-" + strValue);
                                            break;
                                        }
                                    case "Address1":
                                    case "Address2":
                                    case "Phone":
                                        {
                                            string strName = xmlNReader.Name;
                                            xmlNReader.Read();
                                            string strValue = xmlNReader.Value;
                                            //Console.WriteLine(strName +
                                            //  "-" + strValue);
                                            break;
                                        }
                                }

                                break;
                            }
                    }
                }
            }

            objSW.Stop();
            Console.WriteLine("XML parsing using XmlReader - Normal format");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            Console.ReadLine();


        }
    }
}
Output:
XML parsing using XmlDocument - Normal format
TimeElapsed (Stopwatch float):5232.8249ms
TimeElapsed (Stopwatch rounded):5232ms
XML parsing using XDocument - Normal format
TimeElapsed (Stopwatch float):114.9677ms
TimeElapsed (Stopwatch rounded):114ms
XML parsing using XmlReader - Normal format
TimeElapsed (Stopwatch float):42.7921ms
TimeElapsed (Stopwatch rounded):42ms

Note: XmlDocument can be considered, if forward and backward reference is unavoidable during the parsing of xml document.

Rule: Use XmlReader for parsing xml document.

C# XPATH vs LINQ

Use Xmlreader for parsing xml document.
Create an xml file as show below, but with 10000 contact elements called as “address_xw_n.xml” and make it available in D driver (d:\).
<AddressBook>
  <Contact>
    <Name>Person1</Name>
    <Address1><![CDATA[Number101]]></Address1>
    <Address2><![CDATA[Address501]]></Address2>
    <Zip>50001</Zip>
    <Phone><![CDATA[999991]]></Phone>
 </Contact>
</AddressBook>
The below code demonstrate the time take to query the above created xml file using xpath and LINQ for the Phone element value “999999996“.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Diagnostics;
using System.Xml.Linq;
namespace QueryXML
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch objSW = null;
            String strNormalFilePath;
            strNormalFilePath = @"d:\address_xw_n.xml";
            XmlDocument xmlNDoc;
            xmlNDoc = new XmlDocument();
            XDocument xNDoc;
            objSW = new Stopwatch();

            objSW.Reset();
            objSW.Start();
            //load the xml
            xmlNDoc.Load(strNormalFilePath);
            //xml query using xpath - Normal format
            objSW.Reset();
            objSW.Start();
            XmlNode xmlQNNode = xmlNDoc.SelectSingleNode(
                "/AddressBook/Contact/Phone[contains(text(),'999999996')]");
            //Console.WriteLine(xmlQNNode.Name +
            //  "-" + xmlQNNode.ChildNodes[0].Value);
            objSW.Stop();
            Console.WriteLine("XML query using xpath - Normal format");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            //xml query using LINQ - Normal format
            //Let us not include the conversion of XmlDocument to XDocument
            xNDoc = XDocument.Parse(xmlNDoc.OuterXml);
            objSW.Reset();
            objSW.Start();
            var linqNQuery = from p in
                                 xNDoc.Root.Elements("Contact").Elements("Phone")
                             where p.Value.Contains("999999996") select p;
            /*foreach (XElement e in linqNQuery)
            {
                Console.WriteLine(e.Name+"-"+e.Value);
            }*/
            objSW.Stop();
            Console.WriteLine("XML query using LINQ - Normal format");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);

            Console.ReadLine();
        }
    }
}

Output:
XML query using xpath - Normal format
TimeElapsed (Stopwatch float):20.0936ms
TimeElapsed (Stopwatch rounded):20ms
XML query using LINQ - Normal format
TimeElapsed (Stopwatch float):0.5082ms
TimeElapsed (Stopwatch rounded):0ms

Rule: Use LINQ for querying xml content.

Thursday, June 17, 2010

Optimizing C# code

Let us try to understand what is called optimizing a code.
Write a simple logging function which shall
write the given string message to a file.

Step #1 - Write the Logger class with a function which will open/create the text file and append the string message along with time stamp and close the file.

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace LoggerTimeElapsed
{

    class Logger
    {
        public string FilePath { get; set; }
        private object m_objLocker;
        public Logger()
        {
            m_objLocker = new object();
            FilePath = @"d:\log.txt";
        }
        //make sure the StreamWriter is closed
        //make sure thr Monitor is exit
        public bool WriteToFile(string strMessage)
        {
            bool blnStatus = false;
            StreamWriter objStreamWriter = null;
            if (strMessage != string.Empty && strMessage != null)
            {
                try
                {
                    Monitor.Enter(m_objLocker);
                    objStreamWriter = new StreamWriter(FilePath, true);
                    //logs only the time and not the date
                    objStreamWriter.WriteLine(
                        DateTime.Now.TimeOfDay + " - " + strMessage
                        );
                    objStreamWriter.Close();
                    Monitor.Exit(m_objLocker);
                    blnStatus = true;
                }
                catch (Exception objEx)
                {
                    //use messagebox if winforms application
                    Console.WriteLine(objEx.StackTrace);
                    if (objStreamWriter != null)
                    {
                        objStreamWriter.Close();
                    }
                    Monitor.Exit(m_objLocker);
                }
                finally
                {
                }
            }
            return blnStatus;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Logger objLogger = null;
            Stopwatch objSW = null;
            int iCount = 100;
            objSW = new Stopwatch();
            objLogger = new Logger();
            objSW.Reset();
            objSW.Start();
            for (int i = 0; i < iCount; i++)
            {
                objLogger.WriteToFile("Unoptimized" + i.ToString());
            }
            objSW.Stop();
            Console.WriteLine("WriteToFile - TimeElapsed");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Step #2 - Measure the time taken by the function to log 100 messages to the text file.In the above code Stopwatch class is used for measuring the time.
Output:
WriteToFile - TimeElapsed
TimeElapsed (Stopwatch float):319.2352ms
TimeElapsed (Stopwatch rounded):319ms
Step #3 - If the time taken by the function does not meet the throughput requirement, then try to optimize the code. So let us now change the Logger class such that, the text file is opened only once and closed only once.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;

namespace LoggerTimeElapsed
{

    class Logger
    {
        public string FilePath { get; set; }
        private StreamWriter m_objStreamWriter = null;
        private object m_objLocker;
        public Logger()
        {
            m_objLocker = new object();
            FilePath = @"d:\log.txt";
        }
        public bool Open()
        {
            bool blnStatus = false;
            try
            {
                m_objStreamWriter = new StreamWriter(FilePath, true);
                blnStatus = true;
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        public bool Close()
        {
            bool blnStatus = false;
            try
            {
                if (m_objStreamWriter != null)
                {
                    m_objStreamWriter.Close();
                    blnStatus = true;
                }
            }
            catch (Exception objEx)
            {
                Console.WriteLine(objEx.StackTrace);
            }
            finally
            {
            }
            return blnStatus;
        }
        //make sure Open is called before calling WriteToFile
        //make sure Close is called once all logging is done
        public bool WriteToFile(string strMessage)
        {
            bool blnStatus = false;
            if (strMessage != string.Empty && strMessage != null)
            {
                try
                {
                    lock (m_objLocker)
                    {
                        if (m_objStreamWriter != null)
                        {
                            //logs only the time and not the date
                            m_objStreamWriter.WriteLine(
                                DateTime.Now.TimeOfDay
                                + " - "
                                + strMessage
                                );
                            blnStatus = true;
                        }

                    }
                }
                catch (Exception objEx)
                {
                    //use messagebox if winforms application
                    Console.WriteLine(objEx.StackTrace);
                }
                finally
                {
                }
            }
            return blnStatus;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Logger objLogger = null;
            Stopwatch objSW = null;
            int iCount = 100;
            objSW = new Stopwatch();
            objLogger = new Logger();
            objLogger.Open();
            objSW.Reset();
            objSW.Start();
            for (int i = 0; i < iCount; i++)
            {
               objLogger.WriteToFile("Optimized" + i.ToString());
            }
            objSW.Stop();
            objLogger.Close();
            Console.WriteLine("WriteToFile - TimeElapsed");
            Console.WriteLine("TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds);
            Console.WriteLine("TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds);
            Console.ReadLine();
        }
    }
}
Step #4 - Measure the time take by the Logger class to log 100 message to the text file.In the above code Stopwatch class is used for measuring the time.
Output:
WriteToFile - TimeElapsed
TimeElapsed (Stopwatch float):4.6166ms
TimeElapsed (Stopwatch rounded):4ms
Step #5 - Analyze the advantages and disadvantages of different approaches and decide the best approach.

Write the code → Measure → Optimize the code → Measure

C# measurement tools

There are two important factor which has to be measured to identify the improvement in the code
  • Time taken to complete some operation
  • Memory usage of the application
Below is the code for measuring memory consumption.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace StructMemoryUsage
{
    struct EmployeeStruct
    {
        public string Name;
        public int EmpId;
    }

    class Program
    {
        static void Main(string[] args)
        {
            long lngGCMemStart = 0;
            long lngGCMemEnd = 0;
            long lngProcessMemStart = 0;
            long lngProcessMemEnd = 0;

            //employee as struct
            EmployeeStruct[] objEmployee;
            int iCount;
            iCount = 100000;

            Process objProcess = Process.GetCurrentProcess();
            //Measure starting point memory
            lngGCMemStart = System.GC.GetTotalMemory(true);
            lngProcessMemStart = objProcess.PrivateMemorySize64;

            objEmployee = new EmployeeStruct[iCount];

            //Measure memory after allocating
            lngGCMemEnd = System.GC.GetTotalMemory(true);
            lngProcessMemEnd = objProcess.PrivateMemorySize64;

            for (int i = 0; i < iCount; i++)
            {
                objEmployee[i].Name = "Emp" + i.ToString();
                objEmployee[i].EmpId = i;
            }
            Console.WriteLine("Struct memory usage.");
            //memoryusage difference
            Console.WriteLine("GC Memory Use:{0} (bytes)",
                lngGCMemEnd - lngGCMemStart);
            Console.WriteLine("Process Memory Use:{0} (bytes)",
                lngProcessMemEnd - lngProcessMemStart);

            Console.WriteLine("GC Memory Start:{0} (bytes)",
                lngGCMemStart.ToString());
            Console.WriteLine("Process Memory Start:{0} (bytes)",
                lngProcessMemStart.ToString());

            Console.WriteLine("GC Memory End:{0} (bytes)",
                lngGCMemEnd.ToString());
            Console.WriteLine("Process Memory End:{0} (bytes)",
                lngProcessMemEnd.ToString());

            Console.ReadLine();
        }
    }
}
Output:
Struct memory usage.
GC Memory Use:802380 (bytes)
Process Memory Use:0 (bytes)
GC Memory Start:126592 (bytes)
Process Memory Start:8937472 (bytes)
GC Memory End:928972 (bytes)
Process Memory End:8937472 (bytes)

Note: Alternatively use perfmon->Add Counters->.NET CLR Memory counters can be used for viewing the memory consumption of selected .NET process.

Below is the code for measuring the time.
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace MeasuringTime
{
    class Program
    {
        static void Main(string[] args)
        {
            long lngStartTime = 0;
            long lngEndTime = 0;
            DateTime dtStartTime;
            TimeSpan tsTimeDiff;
            long lngSWFrequency=0;
            Stopwatch objSW;// Only from .Net 2.0

            //If the computer is kept running continuously
            //for 49 days then the Environment.TickCount would
            //start counting backwards
            //So the most significant bit is extracted
            lngStartTime = Environment.TickCount
                & Int32.MaxValue;
            System.Threading.Thread.Sleep(5000);
            lngEndTime = Environment.TickCount
                & Int32.MaxValue;

            Console.WriteLine("TimeElapsed (TickCount):{0}ms",
                lngEndTime - lngStartTime);
            //Resolution of DateTime.Now is 10+ milliseconds
            dtStartTime = DateTime.Now;
            System.Threading.Thread.Sleep(5000);
            tsTimeDiff = DateTime.Now - dtStartTime;


            Console.WriteLine("TimeElapsed (DateTime):{0}ms",
                tsTimeDiff.TotalMilliseconds);
            //Stopwatch uses the hardware timer if it
            //highresolution timer or else uses the DateTime.Now
            //To check the availability of the high
            //resolution timer use IsHighResolution property
            if (true == Stopwatch.IsHighResolution)
            {
                Console.WriteLine(
                    "HighResolutionTimer is present"
                    );
            }
            else
            {
                Console.WriteLine(
                    "HighResolutionTimer is absent"
                    );
            }

            lngSWFrequency = Stopwatch.Frequency;
            Console.WriteLine(
                "Timer frequency in ticks/second:{0}",
                lngSWFrequency);
            objSW = Stopwatch.StartNew();
            System.Threading.Thread.Sleep(5000);
            objSW.Stop();
            Console.WriteLine(
                "TimeElapsed (Stopwatch float):{0}ms",
                objSW.Elapsed.TotalMilliseconds
                );
            Console.WriteLine(
                "TimeElapsed (Stopwatch rounded):{0}ms",
                objSW.ElapsedMilliseconds
                );

            Console.ReadLine();

        }
    }
}

Output:
TimeElapsed (TickCount):4992ms
TimeElapsed (DateTime):5002ms
HighResolutionTimer is present
Timer frequency in ticks/second:14318180
TimeElapsed (Stopwatch float):5000.7317ms
TimeElapsed (Stopwatch rounded):5000ms

Note: Profiling tools can be used for the measuring time and memory leak.