Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Brad wanted to know whether or not we minded the exception if the key value didn't exist on his original post on the matter The change from Hashtable to Dictionary .  I figured the reason people would use the exception would be for using integral value types in their dictionaries.  The workaround is to use a Nullable type over the top of the integral type, and then you can always have the ability to return a null value.  You'd think the integral type dictionary would be slightly faster (it will at least take slightly less memory), and that Nullable would be a little slower.  I've found that for some reasons Nullable is actually a little bit faster.  Not sure why, but it is about 50% faster over a simple loop.  Maybe it will be smart to use Nullable types instead of integral types (full source at the end).

Some people on the original thread also said that a Try* method would be sufficient.  Well, there is a Try* method.  It is called TryGetValue and returns a bool for found versus not and an out parameter of the type of your value parameter.  Here is a short sample usage of the function, with a full sample at the end.

string[] keysToTest = new string[] { (keyBase + 50).ToString(), (keyBase + keys + 1).ToString() };
foreach(string key in keysToTest) {
    int intValue = 0;
    if ( intDictionary.TryGetValue(key, out intValue) ) {
        Console.WriteLine("Found: {0}", intValue);
    } else {
        Console.WriteLine("Not Found: {0}", key);
    }
}

I think they've done their job already.  Why change the exception logic, when you already have to do extra work to create the generic dictionary, you might as well do a bit of work to switch over to using the TryGetValue method.  One problem is that it isn't obvious (no code breaks) that you are using the indexer and so you might forget to change a piece of legacy code to use TryGetValue where needed.  My original comments are on this matter are here Darnit, they break the Hashtable (err Dictionary) then ask us how we think they should fix it...

using System;
using System.Collections.Generic;

public class NullableVsValueType {
    const int keyBase = 10000000;
    const int keys = 1000000;
   
    private static void Main(string[] args) {
        string[] stringsForKeys = new string[keys];
        int counter = 0;
        Dictionary<string, int> intDictionary = new Dictionary<string, int>();
        Dictionary<string, Nullable<int>> nullableDictionary = new Dictionary<string, Nullable<int>>();

        DateTime start, end;
       
        Console.WriteLine("Start Pre-Processing Strings");
        start = DateTime.Now;
        for(int i = keyBase; i < (keyBase + keys); i++) {
            stringsForKeys[i - keyBase] = i.ToString();
        }
        end = DateTime.Now;
        Console.WriteLine("Pre-Processing Strings Complete: {0}", end - start);
        Console.WriteLine();
       
        Console.WriteLine("Start Int Dictionary");
        start = DateTime.Now; counter = 0;
        for(int i = 0; i < keys; i++) {
            intDictionary[stringsForKeys[i]] = i;
            // counter += intDictionary[stringsForKeys[i]];
        }
        end = DateTime.Now;
        Console.WriteLine("Int Dictionary Complete: {0}, {1}", end - start, counter);
        Console.WriteLine();
       
        Console.WriteLine("Start Nullable<Int> Dictionary");
        start = DateTime.Now; counter = 0;
        for(int i = 0; i < keys; i++) {
            nullableDictionary[stringsForKeys[i]] = i;
            // nullableDictionary[stringsForKeys[i]] = new Nullable<int>(i);
            // counter += nullableDictionary[stringsForKeys[i]].Value;
        }
        end = DateTime.Now;
        Console.WriteLine("Nullable<Int> Dictionary Complete: {0}, {1}", end - start, counter);
        Console.WriteLine();
       
        Console.WriteLine("Using TryGetValue");
        string[] keysToTest = new string[] { (keyBase + 50).ToString(), (keyBase + keys + 1).ToString() };
        foreach(string key in keysToTest) {
            int intValue = 0;
            if ( intDictionary.TryGetValue(key, out intValue) ) {
                Console.WriteLine("Found: {0}", intValue);
            } else {
                Console.WriteLine("Not Found: {0}", key);
            }
        }
    }
}

[Edit: Adding more complete test code for timing both string keying as above, and integer keying.  One user's comments point that the keying is causing changes on his machine that make the integral value dictionary perform better than the Nullable type dictionary.  I can't repro his case, so I'm attempting to standardize the test case so there aren't any discrepancies.  You can get the latest test code here Nullable Versus Integral Data Types (C# 2.0 Test Code)]

Published Friday, April 30, 2004 8:41 PM by Justin Rogers
Filed under:

Comments

Saturday, May 01, 2004 12:14 AM by Daniel O'Connell

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Out of curiosity...what are the results you are seeing when you run this with int keys? I'm seeing Nullable win when the key is a string and integral win when the key is an int. It seems a strange result to me.
Saturday, May 01, 2004 12:59 AM by Justin Rogers

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Below are my results given varying input to the Dictionary. Using both String as a key and Int as a key shows that Nullable always wins still.

Start Pre-Processing Strings
Pre-Processing Strings Complete: 00:00:00.7310512

Start Dictionary<String, Int> Dictionary
Int Dictionary Complete: 00:00:00.9713968, 0

Start Dictionary<String, Nullable<Int>> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.6509360, 0

Start Dictionary<Int, Int> Dictionary
Int Dictionary Complete: 00:00:00.4105904, 0

Start Dictionary<Int, Nullable<Int>> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.2804032, 0

Using TryGetValue
Found: 50
Not Found: 11000001
Saturday, May 01, 2004 2:06 AM by Daniel O'Connell

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Strange, the result is still the opposite on this system. I wonder which is the odd ball.
this is my results for Int keys:

Start Pre-Processing Strings
Pre-Processing Strings Complete: 00:00:00.0156250

Start Int Dictionary
Int Dictionary Complete: 00:00:00.2343750, 0

Start Nullable<Int> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.3437500, 0

Using TryGetValue
Found: 50
Not Found: 11000001


and my results for string:

Start Pre-Processing Strings
Pre-Processing Strings Complete: 00:00:00.9062500

Start Int Dictionary
Int Dictionary Complete: 00:00:01.2656250, 0

Start Nullable<Int> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.6250000, 0

Using TryGetValue
Found: 50
Not Found: 11000001


What are you compiling with? I am doing just csc /optimize+, perhaps there is some minor difference in that?

Also, there are some interesting differences there...my speed for int dictionaries outstripes yours for the most part, however my results for string dictionaries shows your results as faster. Could this perhaps be the result of cpu specific JITing?

These results are rather stable here, I've run both multiple times...this is curious indeed.

Saturday, May 01, 2004 3:12 AM by TrackBack

# re: The change from Hashtable to Dictionary

Saturday, May 01, 2004 4:28 AM by Justin Rogers

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

The only thing I can think is that we aren't running the same test code. I've double checked mine just to make sure that I wasn't doing anything stupid. To humor me, please run the code at:

http://weblogs.asp.net/justin_rogers/articles/124403.aspx

If you are still getting the same results after running the test there then let me know. Since you coded up your int keying test separately (as I didn't supply the code for an int key above), there must be something we are doing that is different. Thanks.
Saturday, May 01, 2004 4:54 PM by Daniel O'Connell

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Well, my code for the int test was basically the same asyours, just with type changes(I took your code, changed stringsForKeys to an int[] and took out i.ToString() for the most part). My results from your posted code, however still show the same thing:

Start Pre-Processing Strings
Pre-Processing Strings Complete: 00:00:00.9375000

Start Dictionary<String, Int> Dictionary
Int Dictionary Complete: 00:00:01.2343750, 0

Start Dictionary<String, Nullable<Int>> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.7812500, 0

Start Dictionary<Int, Int> Dictionary
Int Dictionary Complete: 00:00:00.3437500, 0

Start Dictionary<Int, Nullable<Int>> Dictionary
Nullable<Int> Dictionary Complete: 00:00:00.4062500, 0

Running it several times is showing a fair amount of change on Dictionary<String,Int> sometimes its as much as 1.2 seconds, sometimes as low as .90.

Other tests I've run have shown string, int to take upwards of 12 seconds when using the Add method...its very strange indeed. This must be a manifestation of the JIT right now.

Out of curiosity, what kind of hardware are you running on?

Intel\AMD?

I'm running P4's here...
Saturday, May 01, 2004 7:31 PM by Justin Rogers

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

I ran that on an Intel P4 2.8. I added some comments to the actual test case code that explain my findings after running the tests many times. It does appear that the JITer is very unstable since code reordering, addition of code, changing reference locality (aka, adding a usage of all four of the dictionary's and the string table to AFTER the tests are complete to ensure none of them are GC'ed while the tests are running).
Wednesday, March 04, 2009 7:57 PM by ...

# re: Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...

Interessante Informationen.

Leave a Comment

(required) 
(required) 
(optional)
(required)