Jump to page: 1 24  
Page
Thread overview
December 07

I do not use dmd -run too often, but recently was checking out Nim language, and they also do have run option, plus pretty fast compiler, so checked it out.

Then, I was curious about trends of dmd -run, and did some historical tracking.

The example is simple example from nim website, ported to Python and D. It imports writefln, and std.array (for split), defines a simple data only class, construct array of few objects, do some iterations, as well do some very small compile time generation, and generator (in I used foreach over delegate). It is very small program, with nothing interesting going on, so we are are mostly measuring compiler, linker, parsing few libraries we import, and startup times.

x min wall time [ms] Notes
Python 3.11.6 31.4
Nim 2.0.0 (cached) 139.4
Nim 2.0.0 (uncached) 711.4 cached binary removed before each re-run
gdc 13.2.0-7 1117.0
dmd 2.106.0 728.1
dmd 2.105.3 712.9
dmd 2.104.2 728.2
dmd 2.103.1 714.7
dmd 2.102.2 714.0
dmd 2.101.2 853.0
dmd 2.100.2 842.9
dmd 2.099.1 843.7
dmd 2.098.1 898.1
dmd 2.097.2 771.9
dmd 2.096.1 729.7
dmd 2.095.1 723.1
dmd 2.094.2 1008
dmd 2.093.1 1078
dmd 2.092.1 1073
dmd 2.091.1 790.6
dmd 2.090.1 794.0
dmd 2.089.1 771.1
dmd 2.088.1 802.1
dmd 2.087.1 790.6
dmd 2.086.1 769.4
dmd 2.085.1 822.6
dmd 2.084.1 771.3
dmd 2.083.1 784.0
dmd 2.082.1 765.4
dmd 2.081.2 693.0
dmd 2.080.1 685.5
dmd 2.079.1 650.4
dmd 2.078.3 628.2
dmd 2.077.1 626.3
dmd 2.076.1 618.8
dmd 2.075.1 589.9
dmd 2.074.1 564.3
dmd 2.073.2 574.3
dmd 2.072.2 590.4
dmd 2.071.2 n/a Linker issues
dmd 2.070.2 n/a Linker issues
dmd 2.069.2 n/a Linker issues
dmd 2.065.0 n/a Linker issues
dmd 2.064.0 n/a No std.array.split available

Measurement error is <0.1%. (idle Linux system, performance governor, 240 or more repetitions, minimums taken).

So, not too bad, but not too good either.

We are not regressing too much, but things could be improved: Sizes of imports reduced, Compiler in general speed up, and for -run, some intermediate form cached on disk to speed up parsing, for various imports, or for the final binary.

Environment: Debian Linux amd64, Threadripper 2950X, all inputs and outputs in RAM on tmpfs (including compilers and libraries).

GNU ld (GNU Binutils for Debian) 2.41.50.20231202

Reference code:

#!/usr/bin/env -S dmd -run

struct Person {
  string name;
  int age;
}

auto people = [
  Person("John", 45),
  Person("Kate", 30),
];

void main() {
  import std.stdio : writefln;
  foreach (person; people) {
    writefln("%s is %d years old", person.name, person.age);
  }

  static auto oddNumbers(T)(T[] a) {
    return delegate int(int delegate(ref T) dg) {
      foreach (x; a) {
        if (x % 2 == 0) continue;
        auto ret = dg(x);
        if (ret != 0) return ret;
      }
      return 0;
    };
  }

  foreach (odd; oddNumbers([3, 6, 9, 12, 15, 18])) {
    writefln("%d", odd);
  }

  static auto toLookupTable(string data) {
    import std.array : split;
    bool[string] result;
    foreach (w; data.split(';')) {
      result[w] = true;
    }
    return result;
  }

  enum data = "mov;btc;cli;xor;afoo";
  enum opcodes = toLookupTable(data);

  foreach (o, k; opcodes) {
    writefln("%s", o);
  }
}
#!/usr/bin/env python3

import dataclasses


@dataclasses.dataclass
class Person:
    name: str
    age: int


people = [
    Person(name="John", age=45),
    Person(name="Kate", age=30),
]

for person in people:
    print(f"{person.name} is {person.age} years old")


def oddNumbers(a):
    for x in a:
        if x % 2 == 1:
            yield x


for odd in oddNumbers([3, 6, 9, 12, 15, 18]):
    print(odd)


def toLookupTable(data):
    result = set()
    for w in data.split(";"):
        result.add(w)
    return result


data = "mov;btc;cli;xor;afoo"
opcodes = toLookupTable(data)

for o in opcodes:
    print(o)
#!/usr/bin/env -S /home/user/nim-2.0.0/bin/nim r --warnings:off --hints:off

import std/strformat

type Person = object
    name: string
    age: Natural # Ensures the age is positive

let people = [
  Person(name: "John", age: 45),
  Person(name: "Kate", age: 30),
]

for person in people:
  echo(fmt"{person.name} is {person.age} years old")


iterator oddNumbers[Idx, T](a: array[Idx, T]): T =
  for x in a:
    if x mod 2 == 1:
      yield x

for odd in oddNumbers([3, 6, 9, 12, 15, 18]):
  echo odd


import macros, strutils

macro toLookupTable(data: static[string]): untyped =
  result = newTree(nnkBracket)
  for w in data.split(';'):
    result.add newLit(w)

const
  data = "mov;btc;cli;xor;afoo"
  opcodes = toLookupTable(data)

for o in opcodes:
  echo o
December 07

Not a good outlook indeed...

Nobody cares about speed nowadays, wich is sad

I noticed the same with DUB: https://github.com/dlang/dub/issues/2600

I reported the issue and one of the maintainers dared to say: "I'm tempted to close this as WONTFIX" lol

I picked D because it compile fast, i ended up maintaining my own runtime/std because they are horribly slow and bloated

There needs to be something that tracks performance over time

Rust started this work in 2018 and they keep improving their compiler year after year: https://internals.rust-lang.org/t/rust-compiler-performance-working-group/6934

December 07

On Thursday, 7 December 2023 at 16:32:32 UTC, Witold Baryluk wrote:

>

dmd

Inspecting output of dmd -v, shows that a lot of time is spend on various helpers of writefln. Changing writefln to writeln (and adjusting things so the output is still the same), speeds things a lot:

729.5 ms -> 431.8 ms (dmd 2.106.0)
896.6 ms -> 638.7 (dmd 2.098.1)

Considering that most script like programs will need to do some IO, writefln looks a little bloated (slow to compile). Having string interpolation would probably help a little, but even then 431 ms is not that great.

Also for completeness, golang:

go1.21.4 121 ms

This is using go run.

Way faster.

package main

import (
	"fmt"
	"strings"
)

type Person struct {
	name string
	age  int
}

var people = []Person{
	Person{name: "John", age: 45},
	Person{name: "Kate", age: 30},
}

func oddNumbers(a []int) chan int {
	ch := make(chan int)
	go func() {
		for _, x := range a {
			if x%2 == 0 {
				continue
			}
			ch <- x
		}
		close(ch)
	}()
	return ch
}

func toLookupTable(data string) map[string]bool {
	result := make(map[string]bool)
	for _, w := range strings.Split(data, ";") {
		result[w] = true
	}
	return result
}

func main() {
	for _, person := range people {
		fmt.Printf("%s is %d years old\n", person.name, person.age)
	}

	for odd := range oddNumbers([]int{3, 6, 9, 12, 15, 18}) {
		fmt.Printf("%d\n", odd)
	}

	data := "mov;btc;cli;xor;afoo"
	opcodes := toLookupTable(data)

	for o := range opcodes {
		fmt.Printf("%s\n", o)
	}
}
December 07

On Thursday, 7 December 2023 at 20:39:03 UTC, Witold Baryluk wrote:

>

Also for completeness, golang:
[...]

For completeness, could you please also try Ruby interpreter and Crystal compiler? The same source file is valid for both. The wacky default constructor arguments are there to provide hints for the Crystal's type inference. This may bring some optimism here, because Crystal has a rather slow compiler. Kotlin is slow too.

#!/usr/bin/env ruby

require "set"

class Person
  def initialize(name = "?", age = -1)
    @name = name
    @age = age
  end
  def name
    @name
  end
  def age
    @age
  end
end

people = [
  Person.new("John", 45),
  Person.new("Kate", 30),
]

people.each do |person|
  puts "#{person.name} is #{person.age} years old"
end

def oddNumbers(a)
  a.each {|x| yield x if x % 2 == 1 }
end

oddNumbers([3, 6, 9, 12, 15, 18]) do |odd|
  puts odd
end

def toLookupTable(data)
  return data.split(';').to_set
end

data = "mov;btc;cli;xor;afoo"
opcodes = toLookupTable(data)

opcodes.each {|o| puts o }
December 07

On Thursday, 7 December 2023 at 21:48:29 UTC, Siarhei Siamashka wrote:

>

On Thursday, 7 December 2023 at 20:39:03 UTC, Witold Baryluk wrote:

>

Also for completeness, golang:
[...]

For completeness, could you please also try Ruby interpreter and Crystal compiler? The same source file is valid for both.

Thanks for the interest.

Nice.

Results from same system the initial numbers were gathered on:

x min time [ms] Notes
ruby 3.1.2p20 69.5
crystal 1.9.2 (LLVM 14.0.6) 1471.0
D (ldc2 1.35.0 (DMD v2.105.2, LLVM 16.0.6)) 821.0

Ruby has slightly longer startup than Python, but not too bad. I expect other purely interpreted languages like Perl, PHP, to have similar times.

I included ldc2 compiler, to make it easier to compare to crystal.

ldc2 looks similar to dmd. Faster than gdc (this is mostly to be expected tho), but otherwise similar.

In crystal, it looks like final codegen and linking are responsible for about 2/3 of the time spent. Maybe switching to something like gold or mold linker could be help a little. This should help with dmd too a little.

I also tried to use D -release switch, hoping there will be less to codegen. There is a difference (about 1.5%), but nothing spectacular.

December 07

On Thursday, 7 December 2023 at 20:39:03 UTC, Witold Baryluk wrote:

>

Inspecting output of dmd -v, shows that a lot of time is spend on various helpers of writefln. Changing writefln to writeln (and adjusting things so the output is still the same), speeds things a lot:

Most of Phobos is slow to import. Some of it is brutally slow to import.

My D2 programs come in at about 300ms mostly by avoiding it... but even that is slow compared to the 100ms that was common back in the old days.

December 08

On Thursday, 7 December 2023 at 22:19:43 UTC, Witold Baryluk wrote:

>

Maybe switching to something like gold or mold linker could be help a little. This should help with dmd too a little.

Not just a little; the default bfd linker is terrible. My timings with various linkers (mold built myself) on Ubuntu 22, using a writeln variant, best of 5:

bfd v2.38 gold v1.16 lld v14 mold v2.4
DMD v2.106.0 0.34 0.22 0.18 fails to link
LDC v1.36.0-beta1 0.47 0.24 0.22 0.18

Bench cmdline: dmd -Xcc=-fuse-ld=<bfd,gold,lld,mold> -run bench.d

December 08

On Thursday, 7 December 2023 at 16:32:32 UTC, Witold Baryluk wrote:

>

I do not use dmd -run too often, but recently was checking

Interesting project. Can you make it in the repo? Maybe others will send PRs for other implementation and versions of compilers..
It could be interesting metric. Similar idea of the repo (compilation only) for example here: https://github.com/nordlow/compiler-benchmark

December 08

On Thursday, 7 December 2023 at 16:32:32 UTC, Witold Baryluk wrote:

>

enum data = "mov;btc;cli;xor;afoo";
enum opcodes = toLookupTable(data);

try auto opcodes = toLookupTable(data);

Does nim run toLookupTable at compile time? That sucks, but other languages have no concept of ctfe at all.

>

ldc2 looks similar to dmd.

That will rub many people the wrong way, lol.

December 09

On Friday, 8 December 2023 at 18:38:21 UTC, Kagamin wrote:

>

On Thursday, 7 December 2023 at 16:32:32 UTC, Witold Baryluk wrote:

>

enum data = "mov;btc;cli;xor;afoo";
enum opcodes = toLookupTable(data);

try auto opcodes = toLookupTable(data);

Does nim run toLookupTable at compile time?

Yes it runs at compile time in Nim.

« First   ‹ Prev
1 2 3 4