views:

653

answers:

3

Here's a curiosity I've been investigating. The .NET Dictionary class performs ridiculously fast compared to the STL unordered_map in a test I keep running, and I can't figure out why.

(0.5 seconds vs. 4 seconds on my machine) (.NET 3.5 SP1 vs. Visual Studio 2008 Express SP1's STL)

On the other hand, if I implement my own hash table in C# and C++, the C++ version is about twice as fast as the C# one, which is fine because it reinforces my common sense that native machine code is sometimes faster. (See. I said "sometimes".) Me being the same person in both languages, I wonder what tricks was the C# coder from Microsoft able to play that the C++ coder from Microsoft wasn't? I'm having trouble imagining how a compiler could play such tricks on its own, going through the trouble of optimizing what should look to it to be arbitrary function calls.

It's a simple test, storing and retrieving integers.

C#:

const int total = (1 << 20);
int sum = 0;
Dictionary<int, int> dict = new Dictionary<int, int>();
for(int i = 0; i < total; i++)
{
    dict.Add(i, i * 7);
}

for(int j = 0; j < (1 << 3); j++)
{
    int i = total;
    while(i > 0)
    {
        i--;
        sum += dict[i];
    }
}
Console.WriteLine(sum);

C++:

const int total = (1 << 20);
int sum = 0;
std::tr1::unordered_map<int, int> dict;
for(int i = 0; i < total; i++)
{
    dict.insert(pair<int, int>(i, i * 7));
}

for(int j = 0; j < (1 << 3); j++)
{
    int i = total;
    while(i > 0)
    {
        i--;
        std::tr1::unordered_map<int, int>::const_iterator found =
            dict.find(i);
        sum += found->second;
    }
}
cout << sum << endl;
+10  A: 

the two versions are not equivalent , your are constructing an iterator in each pass of your C++ while loop. that takes CPU time and throws your results.

Alon
Agreed - try replacing "dict.insert(pair<int, int>(i, i * 7));" with "dict[i] = i * 7;" One less level of gook.
Chris
That, and they're using the array operator in the C# version and the find() method in the C++ version.
Glen
@Glen: The "array operator" is a syntactic convenience that calls the `FindEntry` method. It has no speed advantage.
Ben M
@Ben, nowhere in my version of the STL is there a FindEntry method. Also the implementations of the subscript operator and the find method are quite different leading me to believe thet one may perform quite differently to the other. I'm not sure which is supposed, but a propre perf test should show that pretty easily
Glen
@Ben, if you were referring to the C# subscript operator, then I'm sorry I picked it up incorrectly. However, the OP should be trying to write the correct code in both implementations, not comparing good C# code with an average C++ implementation
Glen
+2  A: 

There will be some differences at the code level: the fact that the unordered map takes a pair forces the construction of such object, when C# might be faster with passing two parameters in Add.

Another point is the implementation of the hashtables themselves: the implementation of the hashing function, or the way to deal with collisions, will cause different performance patterns.

Throw in alignment and caching, JIT-friendliness of some algorithms, and comparing two different implementations in two different languages becomes very difficult, and the only thing you can compare is the particular task at hand. Try with fewer or more elements, or try to access elements randomly instead of in sequence, and you might see very different results.

small_duck
+3  A: 

You are measuring the cost of explicit memory management. More statistics are available here. This is relevant too. And Chris Sells' attempt to add deterministic finalization to the CLR is notable.

Hans Passant
+1 - for great links.
Roman Boiko