Profiling for performance. (1 Viewer)

ojo

Portal Member
September 19, 2004
47
1
Ulstrup, Denmark
Using NProf 0.0.9-alpha I profiled the MediaPortal 0.0.9 further. What suprised me was that with a standard setup of plugins and using the standard MCE skin as much as 43% of the processing time in the main thread goes to MediaPortal.GUI.Library.GUIControlFactory.Create. Inside GUIControlFactory.Create rough 25% of the total time is going for GetString and 19% going for GetInt. What I also noticed is that GetString is called over 13.000 times and GetInt is called over 10.000 times. I might be wrong but I see these as huge figures compared to the actual settings they are supposed to read.

If you look at the overall usage of processing time you will also notice that System.Xml.XmlNode.SelectSingleNode accounts for rougly 31% of the usage. This may not be wrong, but it suggests that some form of optimization would be preferable.

I don't have any sound suggestions yet, but I guess I'll keep digging.

Until now I've been conserned about the load times, maybe I could give overall performance in the modules/plugins a go. Anyone have any clues about where to start?

Ojo
 

ojo

Portal Member
September 19, 2004
47
1
Ulstrup, Denmark
Finally done profiling some....

Downloaded the new 0.0.0.10 version before starting out.

Here is what i found in at typical run of the unmodified code:


53,52% MediaPortal.GUI.Library.GUIControlFactory::Create
23,69% MediaPortal.GUI.Library.GUIControlFactory::GetString
18,91% MediaPortal.GUI.Library.GUIControlFactory::GetInt
4,46% MediaPortal.GUI.Library.GUIControlFactory::GetHex


Looking at the code i found that for every GUIControl a whole lot of possible XML-elements are checked and read if present from the skin xml-file. Every time an element was tested it was via the SelectSingleNode method of the standard XML library. Allthough using a subset (a sub-node) this obvious takes a whole lot of processing time.

So i spent some time thinking and came up with a possible work-around. Why not cache the XML-elements into a Hashtable using the name of the element as hash. Upon calling the Create method first thing was to cache the elements. Then I changed some (GetString, GetInt, GetHex and GetBool) of the read functions to return from the Hashtable instead of using the SelectSingleNode method.

And the results from an equal typical run. The only differences are the changes described above.


4,14% MediaPortal.GUI.Library.GUIControlFactory::Create
0,27% MediaPortal.GUI.Library.GUIControlFactory::GetString
0,33% MediaPortal.GUI.Library.GUIControlFactory::GetInt
0,21% MediaPortal.GUI.Library.GUIControlFactory::GetHex


No, I didn't believe it either, so a did a second run. But I got approx. same results.

And the code:
Code:
namespace MediaPortal.GUI.Library
{
  public class GUIControlFactory
  {
    // just for making this profiling session easier
    private static bool iUseCache = true;
    
    // private cache for holding settings for each control instead of using SelectSingleNode
    // for each possible setting
    private System.Collections.Hashtable XmlCache = new System.Collections.Hashtable(30);
    
    public GUIControlFactory()
    {
    }

...
    bool GetString(ref XmlNode RootNode, string strTag, ref string strStringValue)
    {
      if (iUseCache) // profiling
      { 
        // new code
        if (!XmlCache.ContainsKey(strTag)) return false;
        string sValue = (string) XmlCache[strTag];
        if (sValue == null) return false;
        if (sValue == "") return false;
        strStringValue = sValue;
      }
      else
      {
        // old code
        XmlNode node = RootNode.SelectSingleNode(strTag);
        if (node == null) return false;
        if (node.InnerText == null) return false;
        if (node.InnerText == "") return false;
        strStringValue = node.InnerText;
      }
      return true;
    }

...
    bool GetInt(ref XmlNode RootNode, string strTag, ref int iValue)
    {
      if (iUseCache) // profiling
      { 
        // new code
        if (!XmlCache.ContainsKey(strTag)) return false;
        string sValue = (string) XmlCache[strTag];
        if (sValue == null) return false;
        if (sValue == "") return false;
        try
        {
          iValue = System.Int32.Parse(sValue);
        }
        catch (Exception)
        {
          return false;
        }
      }
      else
      {
        // old code
        XmlNode node = RootNode.SelectSingleNode(strTag);
        if (node == null) return false;
        if (node.InnerText == null) return false;
        if (node.InnerText == "") return false;
        try
        {
          iValue = System.Int32.Parse(node.InnerText);
        }
        catch (Exception)
        {
          return false;
        }
      }
      return true;
    }


...
    bool GetHex(ref XmlNode RootNode, string strTag, ref long lValue)
    {
      if (iUseCache) // profiling
      { 
        // new code
        if (!XmlCache.ContainsKey(strTag)) return false;
        string sValue = (string) XmlCache[strTag];
        if (sValue == null) return false;
        if (sValue == "") return false;
        try
        {
          lValue = System.Int64.Parse(sValue);
        }
        catch (Exception)
        {
          return false;
        }
      }
      else
      {
        // old code
        XmlNode node = RootNode.SelectSingleNode(strTag);
        if (node == null) return false;
        if (node.InnerText == null) return false;
        if (node.InnerText == "") return false;
        try
        {
          lValue = System.Int64.Parse(node.InnerText, NumberStyles.HexNumber);
        }
        catch (Exception)
        {
          return false;
        }
      }
      return true;
    }

...

    bool GetBoolean(ref XmlNode RootNode, string strTag, ref bool bBoolValue)
    {
      if (iUseCache) // profiling
      { 
        // new code
        if (!XmlCache.ContainsKey(strTag)) return false;
        string sValue = (string) XmlCache[strTag];
        if (sValue == null) return false;
        if (sValue == "") return false;
        try
        {
          if (sValue == "off" || sValue == "no" || sValue == "disabled") bBoolValue = false;
          else bBoolValue = true;
        }
        catch (Exception)
        {
          return false;
        }
      }
      else
      {
        // old code
        XmlNode node = RootNode.SelectSingleNode(strTag);
        if (node == null) return false;
        if (node.InnerText == null) return false;
        if (node.InnerText == "") return false;
  			
        string strEnabled = node.InnerText;
        if (strEnabled == "off" || strEnabled == "no" || strEnabled == "disabled") bBoolValue = false;
        else bBoolValue = true;
      }
      return true;
    }

...

The same can be used for the other Get'ters as well. GetAlignment, GetLong etc. I only did the most time consuming to prove the concept.

Ojo
 

MrMario64

Retired Team Member
  • Premium Supporter
  • April 22, 2004
    822
    1
    50
    Home Country
    Netherlands Netherlands
    just so nobody gets his hopes up..

    MP has been profiled a lot allready.
    And yes this looks nice! thanx for this.

    But it's not that 50% of the cpu time is spent on that as some people may think and has now dropped to 5%.

    80% of the CPU time in MP is spent on rendering the GUI, these tweaks represent the other 20%.
    So the BIG win would be to tweak MP's rendering using better DX9 code/methods.
     

    ojo

    Portal Member
    September 19, 2004
    47
    1
    Ulstrup, Denmark
    MrMario64 said:
    But it's not that 50% of the cpu time is spent on that as some people may think and has now dropped to 5%.

    80% of the CPU time in MP is spent on rendering the GUI, these tweaks represent the other 20%.
    So the BIG win would be to tweak MP's rendering using better DX9 code/methods.

    Coudn't agree more. That's why the profiling above ONLY represents the loadtimes of MP. This is not a general profiling of a "long running" app, just the load time. This is from starting the app (dobbelt-click) to the app comes up and responds to mouse events.

    The reason i looked as this is because the loadtime is usual something that bothers me, and surely it's extremely easy to profile.

    And, just to anwser the obvious next questing. I will also, time given, have a look at the "actual" apps. But as MrMario64 points out, extensive profiling has already been done.

    Ojo
     

    ojo

    Portal Member
    September 19, 2004
    47
    1
    Ulstrup, Denmark
    I also looked at the GUILocalizeStrings. Same idear same consequense.

    The new code uses direct indexing instead of SelectSingleNode. This reduces the internal load of the method by 50% having. Now the only thing that takes time in the method is XmlDocument.Load. And since this is unavoidable....

    New code
    Code:
        static bool LoadMap(string strFileName, ref System.Collections.Hashtable map, bool bDetermineNumberOfChars)
        {
          map.Clear();
          try
          {
            XmlDocument doc = new XmlDocument();
            doc.Load(strFileName);
            if (doc.DocumentElement==null) return false;
            string strRoot=doc.DocumentElement.Name;
            if (strRoot!="strings") return false;
            if (bDetermineNumberOfChars==true)
            {
              int iChars=255;
              XmlNode nodeChars = doc.DocumentElement.SelectSingleNode("/strings/characters");
              if (nodeChars!=null)
              {
                if (nodeChars.InnerText!=null && nodeChars.InnerText.Length>0)
                {
                  try
                  {
                    iChars=Convert.ToInt32(nodeChars.InnerText);
                    if (iChars < 255) iChars=255;
                  }
                  catch(Exception)
                  {
                    iChars=255;
                  }
                  GUIGraphicsContext.CharsInCharacterSet=iChars;
                }
              }
            }
            XmlNodeList list=doc.DocumentElement.SelectNodes("/strings/string");
    
    	// new code start
            // this loop is performed multitude of times and therefore converted to for instead of foreach
            for (int x=0; x < list.Count; x++)
            {
              int iCode = (int)System.Int32.Parse(list[x].ChildNodes[0].InnerText);
              string strLine = list[x].ChildNodes[1].InnerText;
              map[iCode]=strLine;
            }
            return true;
    	// new code end
    
          }
          catch (Exception ex)
          {
            Log.Write("exception loading language {0} err:{1} stack:{2}", strFileName, ex.Message,ex.StackTrace);
            return false;
          }
        }

    And again ONLY load-times of MP, not overall performance.

    Ojo
     

    ojo

    Portal Member
    September 19, 2004
    47
    1
    Ulstrup, Denmark
    MrMario64 said:
    so, how much time did this save for you on load-times?
    all tweaks total.

    Right now i'm down to around half the load time !

    Check the "modded" exe here : http://www.tambournet.dk/moddedrelease.zip

    Will remain there for a couple of dayes.

    But beware it's not a full install. Only the bin-files. So you have to extract and copy into your already installed MP.

    Please post your findings here, so I'll be able to see if it's only on my machine it works.

    Ojo
     

    Frodo

    Retired Team Member
  • Premium Supporter
  • April 22, 2004
    1,518
    121
    52
    The Netherlands
    Home Country
    Netherlands Netherlands
    Ojo,

    I looked at your new GUIControlFactory.cs code in the post above
    But if you cache all XML tags & values then it goes terribly wrong!
    reason :
    1. every xml file describes 1 or more GUI controls
    2. each control has an xposition, y position, width,height etc etc
    3. the xposition/yposition/... are different for each control
    since they specify where on the screen the control should be drawn
    4. Your code seems to cache all tags &values
    in otherwords it caches the <posX> of the very 1st control found
    meaning all controls get the same <posX> value????
    Sounds like a very bad idea

    Frodo
     

    ojo

    Portal Member
    September 19, 2004
    47
    1
    Ulstrup, Denmark
    frodo said:
    Ojo,

    I looked at your new GUIControlFactory.cs code in the post above
    But if you cache all XML tags & values then it goes terribly wrong!
    reason :
    1. every xml file describes 1 or more GUI controls
    2. each control has an xposition, y position, width,height etc etc
    3. the xposition/yposition/... are different for each control
    since they specify where on the screen the control should be drawn
    4. Your code seems to cache all tags &values
    in otherwords it caches the <posX> of the very 1st control found
    meaning all controls get the same <posX> value????
    Sounds like a very bad idea

    Frodo

    Your right. I guess i forgot to post the actual caching. Silly me.

    Here it is
    Code:
        public GUIControl Create(int dwParentId, XmlNode pControlNode, GUIControl reference, bool bLoadReferences)
        {
          if (iUseCache)
          {
            XmlCache.Clear();
            for (int x=0; x < pControlNode.ChildNodes.Count; x++)
            {
              XmlCache.Add(pControlNode.ChildNodes[x].Name, pControlNode.ChildNodes[x].InnerText);
            }
          }
    
          int dwPosX = 0, dwPosY = 0;
          int dwWidth = 0, dwHeight = 0;
          int dwID = 0, left = 0, right = 0, up = 0, down = 0;
    ...

    The above also explains how it works. Since only the processed control itself is cached, and the cache is cleared before filling the cache, there is no mixup.

    It actually works quite fine. If you check the bins in the posted URL you'll see it actually works.

    Ojo
     

    Users who are viewing this thread

    Top Bottom