Sunday, March 22, 2009

CSharp Performance – Cast vs Interface

Writing high performance applications is a challenge since you can do the same thing in so many ways. Almost everything you code can be done in many ways that are slow but can be improved to be faster. But on the contrary to this article is my experience on building high traffic web sites, is that it is almost never the application layer that is slow but almost always the data access and text/XML parsing.
But anyway, I spend so much time on thinking how code can be faster and more slimmed ☺

Since I develop more and more abstract and generic functionality using techniques such as Generics and Interfaces, I think of how this will improve both readability and performance. What I want to show in this article, is the performance benefits of using a Interface as a input type instead of an Object.
Always when you use Object as the parameter data type, to create an abstract or dynamic method you need to Cast the object into your desired class. This cast will be negative on the execution time and also add risk for errors. I also think it is negative on readability since you can’t really see the desired input type.

Performance test:
In this performance test I have a basic class that implements an interface. I have two methods that execute the test methods doing a cast, and using the interface methods.
The test object is created 1.000.000 times for each method, which are looped 10 times to compare execution times. As you can see the fastest way is to use an Interface to execute the method. On my machine the Interface method takes 7-8ms, and the cast method takes 15-22ms.



Here is the code:


#region
using System;
using System.Diagnostics;
#endregion
namespace InterfaceTest
{
internal class Program
{
private static void Main(string[] args)
{
Stopwatch stopWatch = new Stopwatch();
TimeSpan ts;
for (int i = 0; i < 10; i++)
{
stopWatch.Start();
TestCast();
stopWatch.Stop();
ts = stopWatch.Elapsed;
string elapsedTime = String.Format("Casting {0:00}:{1:00}:{2:00}sec.{3:00}ms",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds/10);
Console.WriteLine(elapsedTime, "RunTime Cast");
stopWatch.Reset();
stopWatch.Start();
TestInterface();
stopWatch.Stop();
ts = stopWatch.Elapsed;
elapsedTime = String.Format("Interface {0:00}:{1:00}:{2:00}sec.{3:00}ms",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds/10);
Console.WriteLine(elapsedTime, "RunTime Interface");
}
Console.ReadKey();
}
private static void TestInterface()
{
for (int i = 0; i < 1000000; i++)
{
TestClass tc = new TestClass();
DoInterface(tc);
}
}
private static void DoInterface(ITest tc)
{
tc.Read("hello speed test");
tc.DoIt("hello world");
}
private static void TestCast()
{
for (int i = 0; i < 1000000; i++)
{
TestClass tc = new TestClass();
DoCast(tc);
}
}
private static void DoCast(object obj)
{
TestClass tc = obj as TestClass;
tc.Read("hello speed test");
tc.DoIt("hello world");
}
#region Nested type: ITest
public interface ITest
{
bool DoIt(string testString);
void Read(string str);
}
#endregion
#region Nested type: TestClass
public class TestClass : ITest
{
private string _testString;
public TestClass()
{
_testString = "empty";
}
#region ITest Members
public void Read(string str)
{
_testString = str;
}
public bool DoIt(string testString)
{
if (testString == _testString)
{
return true;
}
else
{
return false;
}
}
#endregion
}
#endregion
}
}