Thread overview
GDC vs dmd speed
Oct 14, 2013
Spacen Jasset
Oct 14, 2013
jerro
Oct 14, 2013
bearophile
Oct 15, 2013
Spacen Jasset
Oct 14, 2013
Walter Bright
Oct 15, 2013
Spacen Jasset
Oct 15, 2013
Paul Jurczak
Oct 15, 2013
John Colvin
October 14, 2013
Hello,

Whilst porting some C++ code I have discovered that the compiled output from the gdc compiler seems to be 47% quicker than the dmd compiler. The code I believe that is the 'busy' code is below. Although I could provide a complete test if anyone is interested. Is this an expected result and/or is there something I could change to make the compilers perform similarly. The function render_minecraft gets called repeatedly to render single frames. framebufindex is a simple function to return a buffer index. Perhaps it is not being inlined? rgba is another simple function.

further details are:

dmd32 v2.063.2
with flags: ["-O", "-release", "-noboundscheck", "-inline"]

gdc 4.6 (0.29.1-4.6.4-1ubuntu4) Which I assume might be v2.020?
with flags: ["-O2"]



// render the next frame into the given 'frame_buf'
void render_minecraft(void * private_renderer_data, uint32_t * frame_buf)
{
    render_info * info = cast(render_info *)private_renderer_data;
    const float pi = 3.14159265f;

    float dx = cast(float)(Clock.currSystemTick.length % (TickDuration.ticksPerSec * 10)) / (TickDuration.ticksPerSec * 10);
    float xRot = sin(dx * pi * 2) * 0.4f + pi / 2;
    float yRot = cos(dx * pi * 2) * 0.4f;
    float yCos = cos(yRot);
    float ySin = sin(yRot);
    float xCos = cos(xRot);
    float xSin = sin(xRot);

    float ox = 32.5f + dx * 64;
    float oy = 32.5f;
    float oz = 32.5f;

    for (int x = 0; x < width; ++x) {
        float ___xd = cast(float)(x - width / 2) / height;
        for (int y = 0; y < height; ++y) {
            float __yd = cast(float)(y - height / 2) / height;
            float __zd = 1;

            float ___zd = __zd * yCos + __yd * ySin;
            float _yd = __yd * yCos - __zd * ySin;

            float _xd = ___xd * xCos + ___zd * xSin;
            float _zd = ___zd * xCos - ___xd * xSin;

            uint32_t col = 0;
            uint32_t br = 255;
            float ddist = 0;
            float closest = 32;

            for (int d = 0; d < 3; ++d) {
                float dimLength = _xd;
                if (d == 1)
                    dimLength = _yd;
                if (d == 2)
                    dimLength = _zd;

                float ll = 1 / (dimLength < 0 ? -dimLength : dimLength);
                float xd = (_xd) * ll;
                float yd = (_yd) * ll;
                float zd = (_zd) * ll;

                float initial = ox - cast(int)ox;
                if (d == 1)
                    initial = oy - cast(int)oy;
                if (d == 2)
                    initial = oz - cast(int)oz;
                if (dimLength > 0)
                    initial = 1 - initial;

                float dist = ll * initial;

                float xp = ox + xd * initial;
                float yp = oy + yd * initial;
                float zp = oz + zd * initial;

                if (dimLength < 0) {
                    if (d == 0)
                        xp--;
                    if (d == 1)
                        yp--;
                    if (d == 2)
                        zp--;
                }

                while (dist < closest) {
                    uint tex = info.map[mapindex(xp, yp, zp)];

                    if (tex > 0) {
                        uint u = cast(uint32_t)((xp + zp) * 16) & 15;
                        uint v = (cast(uint32_t)(yp * 16) & 15) + 16;
                        if (d == 1) {
                            u = cast(uint32_t)(xp * 16) & 15;
                            v = (cast(uint32_t)(zp * 16) & 15);
                            if (yd < 0)
                                v += 32;
                        }

                        uint32_t cc = info.texmap[u + v * 16 + tex * 256 * 3];
                        if (cc > 0) {
                            col = cc;
                            ddist = 255 - cast(int)(dist / 32 * 255);
                            br = 255 * (255 - ((d + 2) % 3) * 50) / 255;
                            closest = dist;
                        }
                    }
                    xp += xd;
                    yp += yd;
                    zp += zd;
                    dist += ll;
                }
            }

            const uint32_t r = cast(uint32_t)(((col >> 16) & 0xff) * br * ddist / (255 * 255));
            const uint32_t g = cast(uint32_t)(((col >> 8) & 0xff) * br * ddist / (255 * 255));
            const uint32_t b = cast(uint32_t)(((col) & 0xff) * br * ddist / (255 * 255));

            frame_buf[framebufindex(x, y)] = rgba(r, g, b);
        }
    }
}



October 14, 2013
> Although I could provide a complete test if anyone is interested. Is this an expected result and/or is there something I could change to make the compilers perform similarly.

Maybe you could do something to make the code compiled with DMD
perform better, but it is not unusual for GDC to produce significantly faster
code than DMD.

October 14, 2013
Spacen Jasset:

>     const float pi = 3.14159265f;
>
>     float dx = cast(float)(Clock.currSystemTick.length % (TickDuration.ticksPerSec * 10)) / (TickDuration.ticksPerSec * 10);
>     float xRot = sin(dx * pi * 2) * 0.4f + pi / 2;
>     float yRot = cos(dx * pi * 2) * 0.4f;
>     float yCos = cos(yRot);
>     float ySin = sin(yRot);
>     float xCos = cos(xRot);
>     float xSin = sin(xRot);
>
>     float ox = 32.5f + dx * 64;
>     float oy = 32.5f;
>     float oz = 32.5f;
>
>     for (int x = 0; x < width; ++x) {
>         float ___xd = cast(float)(x - width / 2) / height;
>         for (int y = 0; y < height; ++y) {
>             float __yd = cast(float)(y - height / 2) / height;
>             float __zd = 1;


The performance difference between the DMD and GDC compile is kind of expected for FP-heavy code. Also try the new LDC2 compiler (ldmd2 for the same compilation switches) that sometimes is better than GDC.

More comments:
- There is a PI in std.math (but it's not a float);
- Add immutable/const to every variable that doesn't need to change. This is a good habit like washing your hands before eating;
- "for (int x = 0; x < width; ++x)" ==> "foreach (immutable x; 0 .. width)";
- I suggest to avoid many leading/trailing newlines in identifier names;
- It's probably worth replacing all those "float" with another name, like "FP" and then define "alias FP = float;" at the beginning. So you can see how much performance you lose/gain using floats/doubles. In many cases in my code there is no difference, but float are less precise. Floats can be useful when you have many of them, in a struct or array. Floats can also be useful when you call certain numerical functions that compute their result by approximation, but on some CPUs sin/cos are not among those functions.

Bye,
bearophile
October 14, 2013
On 10/14/2013 12:24 PM, Spacen Jasset wrote:
> dmd32 v2.063.2
> with flags: ["-O", "-release", "-noboundscheck", "-inline"]
>
> gdc 4.6 (0.29.1-4.6.4-1ubuntu4) Which I assume might be v2.020?
> with flags: ["-O2"]

dmd uses the x87 for 32 bit code for floating point, while gdc uses the SIMD instructions, which are faster.

For 64 bit code, dmd uses SIMD instructions too.

October 15, 2013
On Monday, 14 October 2013 at 19:24:27 UTC, Spacen Jasset wrote:
> Hello,
>
> Whilst porting some C++ code I have discovered that the compiled output from the gdc compiler seems to be 47% quicker than the dmd compiler.
....

Here is a few more data points for microbenchmarks of simple functions (Project Euler), which supports an observation (disclaimer: my microbenchmark is not a guarantee of your code performance, etc.) that the fastest code is produced by LDC, then GDC and DMD is the slowest one.

Tested on Xubuntu 13.04 64-bit Core i5 3450S 2.8GHz.

--------------------
Test 1:

// 454ns  LDC 0.11.0: ldmd2 -m64 -O -noboundscheck -inline -release
// 830ns  GDC 4.8.1: gdc -m64 -march=native -fno-bounds-check -frename-registers -frelease -O3
// 1115ns  DMD64 2.063.2: dmd -O -noboundscheck -inline -release


int e28_0(int N = 1002) {
	int diagNumber = 1;					
	int sum        = diagNumber;	

	for (int width = 2; width < N; width += 2)	
		for (int j = 0; j < 4; ++j) {			
			diagNumber += width;				
			sum        += diagNumber;			
		}

	return sum;
}

--------------------
Test 2:

// 118ms   LDC 0.11.0: ldmd2 -m64 -O -noboundscheck -inline -release
// 125ms   GDC 4.8.1: gdc -m64 -march=native -fno-bounds-check -frename-registers -frelease -O3
// 161ms   DMD64 2.063.2: dmd -O -noboundscheck -inline -release

bool isPalindrome(string s) {return equal(s, s.retro);}

int e4(int N = 1000) {
   int nMax = 0;

   foreach (uint i; 1..N)
      foreach (uint j; i..N)
         if (isPalindrome(to!string(i*j))  &&  i*j > nMax)
            nMax = i*j;

   return nMax;
}

--------------------
Test 3:

// 585us   LDC 0.11.0: ldmd2 -m64 -O -noboundscheck -inline -release
// 667us   GDC 4.8.1: gdc -m64 -march=native -fno-bounds-check -frename-registers -frelease -O3
// 853us   DMD64 2.063.2: dmd -O -noboundscheck -inline -release

int e67_0(string fileName = r"C:\Euler\data\e67.txt") {
   // Read triangle numbers from file.
   int[][] cell;

   foreach (line; splitLines(cast(char[]) read(fileName))) {
      int[] row;

      foreach (token; std.array.splitter(line))
         row ~= [to!int(token)];

      cell ~= row;
   }

   // Compute maximum value partial paths ending at each cell.
   foreach (y; 1..cell.length) {
      cell[y][0] += cell[y-1][0];

      foreach (x; 1..y)
         cell[y][x] += max(cell[y-1][x-1], cell[y-1][x]);

      cell[y][y] += cell[y-1][y-1];
   }

   // Return the maximum value terminal path.
   return cell[$-1].reduce!max;
}

--------------------
Here is the relative to LDC code speed averaged over these three test (larger number is slower):
LDC 1.00
GDC 1.34
DMD 1.76
October 15, 2013
On Monday, 14 October 2013 at 19:24:27 UTC, Spacen Jasset wrote:
> gdc 4.6 (0.29.1-4.6.4-1ubuntu4) Which I assume might be v2.020?
> with flags: ["-O2"]

That's a really old gdc. If you can, upgrade to ubuntu 13.10 and you'll get a more up-to-date version. Alternatively, build from source: http://gdcproject.org/wiki/Installation/General    It'll take an age to run the compilation, but it's not hard to do.
October 15, 2013
On 14/10/2013 22:06, bearophile wrote:
> Spacen Jasset:
>
>>     const float pi = 3.14159265f;
>>
>>     float dx = cast(float)(Clock.currSystemTick.length %
>> (TickDuration.ticksPerSec * 10)) / (TickDuration.ticksPerSec * 10);
>>     float xRot = sin(dx * pi * 2) * 0.4f + pi / 2;
>>     float yRot = cos(dx * pi * 2) * 0.4f;
>>     float yCos = cos(yRot);
>>     float ySin = sin(yRot);
>>     float xCos = cos(xRot);
>>     float xSin = sin(xRot);
>>
>>     float ox = 32.5f + dx * 64;
>>     float oy = 32.5f;
>>     float oz = 32.5f;
>>
>>     for (int x = 0; x < width; ++x) {
>>         float ___xd = cast(float)(x - width / 2) / height;
>>         for (int y = 0; y < height; ++y) {
>>             float __yd = cast(float)(y - height / 2) / height;
>>             float __zd = 1;
>
>
> The performance difference between the DMD and GDC compile is kind of
> expected for FP-heavy code. Also try the new LDC2 compiler (ldmd2 for
> the same compilation switches) that sometimes is better than GDC.
>
> More comments:
> - There is a PI in std.math (but it's not a float);
> - Add immutable/const to every variable that doesn't need to change.
> This is a good habit like washing your hands before eating;
> - "for (int x = 0; x < width; ++x)" ==> "foreach (immutable x; 0 ..
> width)";
> - I suggest to avoid many leading/trailing newlines in identifier names;
> - It's probably worth replacing all those "float" with another name,
> like "FP" and then define "alias FP = float;" at the beginning. So you
> can see how much performance you lose/gain using floats/doubles. In many
> cases in my code there is no difference, but float are less precise.
> Floats can be useful when you have many of them, in a struct or array.
> Floats can also be useful when you call certain numerical functions that
> compute their result by approximation, but on some CPUs sin/cos are not
> among those functions.
>
> Bye,
> bearophile
Thank you. I may take up some of those suggestions. It was a direct port of some c++ hence the style.
October 15, 2013
On 14/10/2013 22:22, Walter Bright wrote:
> On 10/14/2013 12:24 PM, Spacen Jasset wrote:
>> dmd32 v2.063.2
>> with flags: ["-O", "-release", "-noboundscheck", "-inline"]
>>
>> gdc 4.6 (0.29.1-4.6.4-1ubuntu4) Which I assume might be v2.020?
>> with flags: ["-O2"]
>
> dmd uses the x87 for 32 bit code for floating point, while gdc uses the
> SIMD instructions, which are faster.
>
> For 64 bit code, dmd uses SIMD instructions too.
>
Thanks Walter. I shall find a 64 bit system at some point to compare.