среда, 11 декабря 2013 г.

ServiceStack performance on mono part4


Today I again tried to increase performance of ServiceStack on the Mono. In the first part I noted that profiler showed large amount of calls and execution time of Hashtable:GetHash(), SimpleCollator:CompareInternal() and Char:ToLower() methods. To understand why these methods works slow I checked the call stack and found that most of the calls are maden from HttpHeadersCollection class. When I looked inside the source and saw that HttpHeadersCollection uses InvariantCultureIgnoreCase string comparison instead of OrdinalIgnoreCase which is more suitable when comparing names of headers (because they do not need be linguistic equivalent) and should be more performant

To be sure of Hashtable and Dictionary performance with various StringComparing options I wrote simple benchmark. It adds 100 000 strings and than tries to get them one by one for every StringComparing options. The original idea of test code I get from here. My test is slightly modified.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections;

namespace DictPerfomanceTest
{
 class ComparerInfo
 {
  public string Name { get; set;}

  public StringComparer Comparer { get; set;}

  public ComparerInfo(string name, StringComparer comparer)
  {
   Name = name;
   Comparer = comparer;
  }
 }

 class MainClass
 {
  const int nCount=100000;
  const string prefix = "SomeSomeString";

  static readonly ComparerInfo[] Comparers=new ComparerInfo[]
  {
   new ComparerInfo("CurrentCulture",StringComparer.CurrentCulture),
   new ComparerInfo("CurrentCultureIgnoreCase",StringComparer.CurrentCultureIgnoreCase),
   new ComparerInfo("InvariantCulture",StringComparer.InvariantCulture),
   new ComparerInfo("InvariantCultureIgnoreCase",StringComparer.InvariantCultureIgnoreCase),
   new ComparerInfo("Ordinal",StringComparer.Ordinal),
   new ComparerInfo("OrdinalIgnoreCase",StringComparer.OrdinalIgnoreCase)
  } ;

  public static void Main (string[] args)
  {
   foreach(var ci in Comparers)
   {
    Console.WriteLine ("Hashtable: {0}", ci.Name);
    Run (new Hashtable (ci.Comparer));
   }

   foreach(var ci in Comparers)
   {
    Console.WriteLine ("Dictionary: {0}", ci.Name);
    Run (new Dictionary<string,string> (ci.Comparer));
   }
  }

  private static void Run(Hashtable hashtable)
  {
   for(int i = 0; i < nCount; i++)
   {
    hashtable.Add(prefix+i.ToString(), i.ToString());
   }

   Stopwatch sw = new Stopwatch();
   sw.Start();
   for (int i = 0; i < nCount; i++)
   {
    string a = (string)hashtable[prefix+i.ToString()];
   }
   sw.Stop();
   Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);
  }

  private static void Run(Dictionary<string, string> dictionary)
  {
   for(int i = 0; i < nCount; i++)
   {
    dictionary.Add(prefix+i.ToString(), i.ToString());
   }

   Stopwatch sw = new Stopwatch();
   sw.Start();
   for (int i = 0; i < nCount; i++)
   {
    string a = dictionary[prefix+i.ToString()];
   }
   sw.Stop();
   Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds);
  }

 }
}

Comparison OptionHashtable time (ms)Dictionary time (ms)
CurrentCulture19 13116 030
CurrentCultureIgnoreCase20 45816 587
InvariantCulture18 35915 161
InvariantCultureIgnoreCase21 12816 192
Ordinal5846
OrdinalIgnoreCase7373

What can I say? Don't use InvariantCulture or Culture-depended comparison in mono if you don't need it really! In most cases when you use string as dictionary key you can safely use Ordinal or OrdinalIgnoreCase string comparing options. For example names of caching keys in Redis, paths, names of configuration elements in xml are good candidates for Ordinal comparison. By default Dictionary uses Ordinal and Hashtable uses OrdinalIgnoreCase comparison for strings, but don't forget to pass these options to String.Compare(), String.StartWith(), String.EndWith() methods if you want to run you software fast and more predictable

Very good explanation about differencies about InvariantCulture and Ordinal comparison you can read here. In two lines of code it's looking like this:

Console.WriteLine(String.Equals("æ", "ae", StringComparison.Ordinal)); // Prints false  
Console.WriteLine(String.Equals("æ", "ae", StringComparison.InvariantCulture)); // Prints true

I changed HttpHeadersCollection in the commit and made a pull request to mono. Hope it will be reviewed and approved. Also I am going to change hashing functions for HttpRequest headers, first tests shows 3x to 6x performance improvement of ordinal case insensitive hash function without any changes of hashing algorithm

Links:


ServiceStack performance in mono. Part 1
ServiceStack performance in mono. Part 2
ServiceStack performance in mono. Part 3

Комментариев нет:

Отправить комментарий