Keyvan Nayyeri

God breathing through me

Use Generics to Improve Performance

Generics were of one of main new features in .NET 2.0 languages. In .NET 1.x developers had to use System.Collections non-Generic collections to save their objects but no type checking were occurred for them. Therefore they had to do a type casting when they were retrieving their objects from collections.

Now that Microsoft introduced Generics as a type specific alternative for those old means, life is pretty easier for each developer. One of most important reasons to use Generics instead of non-Generic collections is performance.

As you don't need to cast an object as what you did when you read it from a collection such as ArrayList your performance will be improved.

Obviously each and every developer should try to use Generics in his code but what's the actual effect of using them on performance?

In this post I want to write some test codes with Generics and non-Generic collections then run them and use Visual Studio performance sessions to get some statistical information and compare these two new and old approaches to show you the influence of using Generics on performance.

How to test

Probably you already know doing a performance test on an abstract object which doesn't need any interaction with IO and database should avoid using these operations to give a good result.

So to have a correct comparison between Generics and non-Generic collections I'll create and add some objects to them then will retrieve these objects from them later.

To have a better result I'll put all above codes in some iterations while I'm running these codes. I also will use Visual Studio performance reports to log everything about functions I used in my code.

Finally I'll use Excel to analyze returned results from my reports.

These are some necessary definitions for parameters I'll use to analyze my performance reports and will refer to their names later in this post (rewritten from Visual Studio tooltips):

  • Elapsed Exclusive Time: Time spent in this function (msec)
  • Application Exclusive Time: Time spent in this function but not in the kernel (msec)
  • Elapsed Inclusive Time: Time spent in this function and its children (msec)
  • Application Inclusive Time: Time spent in this function and its children, but not in the kernel (msec)

In this post I'll compare some real world common scenarios such as List<T> against ArrayList as well as Queue<T> against Queue.

List<T> vs. ArrayList

ArrayList was one of common .NET 1.x objects. It can save a collection of objects regardless of their types. When you want to retrieve your objects, should cast them to original type.

List<T>is a Generic collection which is very similar to ArrayList and can be a good alternative for it. The main difference between List<T> and ArrayList is the first one is type specific and second is non-Generic. Note that in all situations you can't replace ArrayList with List<T> but in many cases it's possible and recommended.

Test

I begin my test by creating a very simple Product class. This class has three properties:

  • ID (int): Identifier of this class. I' will use random numbers for it.
  • Serial (string): I'll use a Guid value for this property.
  • Name (string): I'll combine constant strings with the ID of objects to set this property.

public class Product

{

    private int id;

 

    public int ID

    {

        get { return id; }

        set { id = value; }

    }

 

    private string serial;

 

    public string Serial

    {

        get { return serial; }

        set { serial = value; }

    }

 

    private string name;

 

    public string Name

    {

        get { return name; }

        set { name = value; }

    }

 

}

Later I want to use object instances of this class in my code to add them to my collections. I write a simple method to generate some random values and use them to create instances of Product class and set their values then add them to a List<Product> and an ArrayList. Finally I'll call ListOfTMethod() and ListMethod() methods to iterate through these two collections to log their performance.

private void Process()

{

    List<Product> List1 = new List<Product>();

    ArrayList List2 = new ArrayList();

 

    Random rand = new Random();

    for (int i = 0; i < 10000; i++)

    {

        Product product = new Product();

        int id = rand.Next(10000);

        product.ID = id;

 

        Guid guid = Guid.NewGuid();

        product.Serial = guid.ToString();

 

        product.Name = "Product #" + id.ToString();

 

        List1.Add(product);

        List2.Add(product);

    }

 

    ListOfTMethod(List1);

    ArrayListMethod(List2);

}

It's time to write ListOfTMethod() and ListMethod() methods. These two methods help to log the performance of my collections. They simply get both collections and append string value of the properties contained in them and return final string value.

private void ListOfTMethod(List<Product> List)

{

    foreach (Product product in List)

    {

    }

}

 

private void ArrayListMethod(ArrayList List)

{

    foreach (Product product in List)

    {

    }

}

Now I run my code but before start running I create a performance session to log their performance during run.

Analyze

After running the code everything is logged and I can export them to .CVS files and analyze them in Excel.

I open my file in Excel and remove unnecessary rows to keep only two rows that contain the information of my methods.

This is the information I got from my performance reports:

Obviously using Generics could improve my performance. The result above is just for a very simple small application with simple classes and iterations. As this process is just a very small part of a larger application this difference can have a larger effect on whole performance.

Queue<T> vs. Queue

Queue is another member of System.Collections which has a new alternative in Generics. Queue is FIFO data structure and is one of key concepts of Data Structures and Algorithm Analysis.

Test

This test is very similar to what I did for List and ArrayList. I add Product objects to Queue<Product> and a normal Queue and call two different methods for them.

private void Process()

{

    Queue<Product> Queue1 = new Queue<Product>();

    Queue Queue2 = new Queue();

 

    Random rand = new Random();

    for (int i = 0; i < 10000; i++)

    {

        Product product = new Product();

        int id = rand.Next(10000);

        product.ID = id;

 

        Guid guid = Guid.NewGuid();

        product.Serial = guid.ToString();

 

        product.Name = "Product #" + id.ToString();

 

        Queue1.Enqueue(product);

        Queue2.Enqueue(product);

    }

 

    QueueOfTMethod(Queue1);

    QueueMethod(Queue2);

}

This is the source code of QueueOfTMethod() and QueueMethod() methods:

private void QueueOfTMethod(Queue<Product> queue)

{

    while (queue.Count > 0)

    {

        Product product = queue.Dequeue();

    }

}

 

private void QueueMethod(Queue queue)

{

    while (queue.Count > 0)

    {

        Product product = queue.Dequeue() as Product;

    }

}

Analyze

Result of performance reports is as follows:

You see the third diagram (Elapsed Inclusive Time) has a different result.  But what's the reason?  I guess the reason is hidden in the algorithms that should be used to  perform some tasks on Queue data structure.  If you do a same test on Stack data structure (which needs same kinds of algorithms), will get same result.  Probably children functions of QueueOfTMethod() that will do some internal operations on Queue<Product> have more cost than their corresponding children functions in QueueMethod().

Conclusion

Use Generics to improve your performance. You saw their influence in my tests.  In larger real world applications this influence is greater.

7 Comments
Via Keyvan Nayyeri ( full post here ): Generics were of one of main new features in .NET 2.0 languages.

David Keaveny
Aug 21, 2006 5:18 PM
#
I'm a little confused by your opening statement - you say that in .NET 1.x, developers had to use strongly-typed collections, but didn't get any type checking? Surely, either you inherit from CollectionBase or implement some interface such as IList, and in your methods such as Add, AddRange or Insert you specify the object type (thus enforcing type safety), or you just use the vanilla collections like ArrayList or Queue, and accept that they store entries as Object types. You could also use the OnValidate events to ensure that certain type conditions are met. You can't have a strongly-typed collection that doesn't give you type-checking! Now admittedly, even when you have a strongly-typed collection inheriting from CollectionBase or whatever, you do have to cast the result retrieved from the InnerList or however you're storing results, so your performance figures probably are still spot on; but your opening statement probably needs a little work.

Keyvan Nayyeri
Aug 21, 2006 7:52 PM
#

You're absolutely right :-)

I had to use "non-Generic" instead of "strongly typed" collections in my introduction.  The reason is maybe because I wrote it before my code.

However thanks for pointing it :-)


Jon Skeet
Aug 22, 2006 12:21 PM
#
"You saw their influence in my tests. In larger real world applications this influence is greater." No. In real world applications the influence is almost non-existent, because you actually *do some work* during the loop. The code you've posted above does nothing - it's timing *just* the iteration part, which is very rarely significant. The main benefit of generics isn't in performance, at least for reference types - it's in making the code more expressive, and gaining compile-time type safety. Ironically, there *is* a significant performance improvement to be had with generics when it comes to value types, which don't require boxing. Change your tests to use GUID as your type and you'll see a *much* bigger difference. It's very important that you understand where microbenchmarks like these are useful and where they're not. They're *interesting* anyway, but making spurious claims about their importance within real applications is very misleading.

Keyvan Nayyeri
Aug 22, 2006 2:50 PM
#
Thanks for your reply Jon, I tried this code in a simple iteration to measure the performance of Generics, only in compare with non-Generics when they need a type casting in iterations. Surely there are many parameters role in this scenario but this post just report of what I wanted to get about Generics and isn't an official report ;-) And sure again, performance isn't the primary benefit of using Generics. Thanks for your explanation again.

Technical Musings
Oct 01, 2007 2:19 PM
#
Following up on that last post about using the Queue data structure for FIFO operations, I thought it would be useful to look at a few things to consider before blindly using this classic structure....

Manjula
Jun 18, 2009 6:40 AM
#

This site is Very simple Informatiove on Generis, please check this

Leave a Comment





Ads Powered by Lake Quincy Media Network