Davita
Davita

Reputation: 9124

Why is this D code slower than a similar C# implementation?

I was playing around D and ported a sample project I've written in C# to test how it performs. I won't lie saying that I wasn't suprised seeing D was much slower than C# because that was my first attempt to write something in D.

In short, C# executes the code in 32 seconds, while D requires 54 seconds. The code is a bit large for a snippet, but if anyone can provide a little bit of time to explain why this happened/what's wrong with my D code would be great.

Here's a short piece of my code:

void ProcessReel(int reelIndex, string pattern, Game game)
{
    counter++;
    if(reelIndex == game.Reels.length) 
    {
        Symbol winningSymbol;
        auto patternLength = 0;


        auto p3_1 = pattern[0..3];
        auto p3_2 = pattern[2..5];
        auto p4_1 = pattern[0..4];
        auto p4_2 = pattern[1..5];

        auto pos = GetFromDict(p3_1, p3_2, game);
        if(pos != null)
        {
            winningSymbol = pos._Symbol;
            patternLength = pos.PatternLength;
        }

        pos = GetFromDict(p4_1, p4_2, game);
        if(pos != null)
        {
            winningSymbol = pos._Symbol;
            patternLength = pos.PatternLength;
        }

        if(pattern in patternTable)
        {
            auto combination = patternTable[pattern];
            winningSymbol = combination._Symbol;
            patternLength = combination.PatternLength;
        }

        if(winningSymbol !is null)
        {
            winningSymbol.Combinations++;
        }
        return;
    }

    auto reel = game.Reels[reelIndex];
    for(int i = 0; i < reel.ReelStrip.length; i++)
    {
        auto p = pattern;
        auto sym = reel.ReelStrip[i];
        p ~= std.conv.to!string(sym);
        ProcessReel(reelIndex + 1, p, game);
    }
}

Full code can be downloaded from here

I'm compiling with the following compiler arguments

dmd Test.d -O -release -inline -boundscheck=off

Thanks.

UPDATE

Here's C# code. It was supposed to run in parallel, that's why it has a lock inside, but I made it serial for a bit fair competition, however, even with this lock in place, C# beats D.

public static void ProcessReel(int reelIndex, Dictionary<int, Dictionary<byte, WinPossibility>> dict, string pattern, AnalyzedResult result)
{
    if (reelIndex == result.Game.ReelCollection[0].Reels.Count)  // This is the last reel. Time to analyze those shit :((
    {
        Symbol winningSymbol = null;
        var patternLength = 0;

        var p3_1 = pattern.Substring(0, 3);
        var p3_2 = pattern.Substring(2, 3);
        var p4_1 = pattern.Substring(0, 4);
        var p4_2 = pattern.Substring(1, 4);

        if (result.PatternTable.ContainsKey(p3_1) || result.PatternTable.ContainsKey(p3_2))
        {
            var winCombination = Helper.GetFromDict(p3_1, p3_2, result);
            var symbol = winCombination.Symbol;
            winningSymbol = symbol;
            patternLength = winCombination.PatternLength;
        }

        if (result.PatternTable.ContainsKey(p4_1) || result.PatternTable.ContainsKey(p4_2))
        {
            var winCombination = Helper.GetFromDict(p4_1, p4_2, result);
            var symbol = winCombination.Symbol;
            winningSymbol = symbol;
            patternLength = winCombination.PatternLength;
        }

        if (result.PatternTable.ContainsKey(pattern))
        {
            var winCombination = result.PatternTable[pattern];
            var symbol = winCombination.Symbol;
            winningSymbol = symbol;
            patternLength = winCombination.PatternLength;
        }

        if (winningSymbol != null)
        {
            lock (dict)
            {
                var d = dict[patternLength];
                if (d.ContainsKey(winningSymbol.Code))
                    d[winningSymbol.Code].Combinations += 1;
                else
                    d.Add(winningSymbol.Code, new WinPossibility(winningSymbol, patternLength, 1));
            }
        }

        return;
    }

    var reel = result.Game.ReelCollection[0].Reels[reelIndex];

    //if (reelIndex == 0)
    //{
    //    Parallel.For(0, reel.ReelStrip.Length, r1 =>
    //    {
    //        var p = pattern;
    //        var sym = reel.ReelStrip[r1];
    //        p += sym;

    //        ProcessReel(reelIndex + 1, dict, p, result);
    //    });
    //}
    //else
    //{
        for (int r1 = 0; r1 < reel.ReelStrip.Length; r1++)
        {
            var p = pattern;
            var sym = reel.ReelStrip[r1];
            p += sym;

            ProcessReel(reelIndex + 1, dict, p, result);
        }
    //}
}

UPDATE 2

I just downloaded GDCC 4.9. It helped, calculation time is now down to 38 seconds from 54, but still inferior to .NET/C#...

Upvotes: 3

Views: 257

Answers (1)

Kozzi11
Kozzi11

Reputation: 2413

There is a few things which makes D version slower.

1.)

if(pattern in patternTable) {
    auto combination = patternTable[pattern];

You make 2 lookups here instead of one, you should use this:

auto combination = pattern in patternTable;
if(combination) {
    ...

Same for GetFromDict function

2.) array concatenation is slow it is better to use std.array.Appender

3.) there is no need to use std.container.Array, you could use standart D arrays with appender

4.) to!string(sym) is slow, you colud improve speed for sym in range 0 .. 10 with:

cast(char)('0' + sym)

5.) DMD is slow you should try ldc and gdc with appropeiate flags for eg.:

gdc -O3 -finline -fno-bounds-check  -frelease

With some changes I was able to make it many times faster from almost 50s to 15s

here is my code: modified code

UPDATE

I have some mistakes here is another version which is little slower but should give right results:

http://dpaste.dzfl.pl/cd6a7af786ec

Upvotes: 5

Related Questions