Thread overview
Performance issue with fiber
4 days ago
hanabi1224
3 days ago
seany
1 day ago
Stefan Koch
23 hours ago
jfondren
4 days ago

Hi, I'm new to D lang and encounter some performance issues with fiber, not sure if there's something obviously wrong with my code.

Basically, I found D lang's logical thread communication is quite like Elixir's (process),
I have programs written in both D and Elixir but the D version (release build) is like >5 times slower.

The program is to find first N primes by creating another coroutine for every prime found. And the idea is from the concurrent prime sieve example on golang's homepage.

Elixir code: 1.ex

D code: 1.d

Perf result of calculating first 2000 primes on my laptop.

Elixir: 1.33s user 0.25s system 108% cpu 1.451 total

D(dmd): 10.41s user 34.48s system 361% cpu 12.405 total

D(ldc): 8.45s user 33.06s system 356% cpu 11.638 total

Also attaching code inline:

D

import std;
import core.stdc.stdlib: exit;

__gshared Tid mainTid;
__gshared bool terminated = false;

const int mailBoxSize = 1;

void main(string[] args) {
    auto n = args.length > 1 ? args[1].to!int() : 10;
    auto scheduler = new FiberScheduler;
    scheduler.start({
        mainTid = thisTid();
        setMaxMailboxSize(mainTid, n, OnCrowding.throwException);
        auto filterTid = spawnLinked(&filter, n);
        setMaxMailboxSize(filterTid, mailBoxSize, OnCrowding.block);
        auto generatorTid = spawnLinked(&generate, filterTid);
        for(auto i=0;i<n;i++){
            auto prime = receiveOnly!int;
            writeln(prime);
        }
        terminated = true;
        exit(0);
    });
}

void generate(Tid tid) {
    for (auto i=2;!terminated;i++) {
        tid.send(i);
    }
}

void filter(int nLeft) {
    auto prime = receiveOnly!int;
    mainTid.send(prime);
    if (nLeft > 0) {
        filterInner(prime, nLeft);
    }
}

void filterInner(int prime, int nLeft) {
    auto nextTid = spawnLinked(&filter, nLeft-1);
    setMaxMailboxSize(nextTid, mailBoxSize, OnCrowding.block);
    while(!terminated){
        auto d = receiveOnly!int;
        if (d % prime != 0) {
            nextTid.send(d);
        }
    }
}

Elixir

defmodule App do
  def main(args) do
    n = String.to_integer(Enum.at(args,0,"27"), 10)
    generate(n)
  end

  def generate(n) do
    mainPid = self()
    pid = spawn_link(fn -> filter(mainPid, n) end)
    generateLoop(pid, 2)
  end

  def generateLoop(pid, n) do
    send(pid, {:n, n})
    receive do
      :gen -> generateLoop(pid, n + 1)
    end
  end

  def filter(mainPid, nLeft) do
    receive do
      {:n, n} -> filterInner(mainPid, n, nLeft)
    end
  end

  def filterInner(mainPid, prime, nLeft) do
    send(mainPid, :gen)
    IO.puts("#{prime}")
    if nLeft > 1 do
      pid = spawn_link(fn -> filter(mainPid, nLeft-1) end)
      recieveAndSendToFilter(mainPid, self(), pid, prime)
    else
      System.halt(0)
    end
  end

  def recieveAndSendToFilter(mainPid, rxPid, txPid, prime) do
    receive do
      {:n, n} -> recieveAndSendToFilterInner(mainPid, rxPid, txPid, prime, n)
    end
  end
  def recieveAndSendToFilterInner(mainPid, rxPid, txPid, prime, n) do
    if Integer.mod(n, prime) != 0 do
      send(txPid, {:n, n})
    else
      send(mainPid, :gen)
    end
    recieveAndSendToFilter(mainPid, rxPid, txPid, prime)
  end
end
3 days ago

On Wednesday, 21 July 2021 at 22:51:38 UTC, hanabi1224 wrote:

>

Hi, I'm new to D lang and encounter some performance issues with fiber, not sure if there's something obviously wrong with my code.

[...]

Following.

I am also in need of more information to increase speed of D binaries using parallel code.

1 day ago

On Wednesday, 21 July 2021 at 22:51:38 UTC, hanabi1224 wrote:

>

Hi, I'm new to D lang and encounter some performance issues with fiber, not sure if there's something obviously wrong with my code.

There is your problem.

>
auto scheduler = new FiberScheduler;

The Fiber scheduler will spawn a new fiber for every job.
It will not use a fiber pool. Spawning a new fiber is expensive because of the stack allocation for it.
Also if I recall correctly it will run single-threaded but I am not 100% sure on that.
Just have a look at the running processes ... if you just see one than you are single threaded.

23 hours ago

On Saturday, 24 July 2021 at 09:17:47 UTC, Stefan Koch wrote:

>

On Wednesday, 21 July 2021 at 22:51:38 UTC, hanabi1224 wrote:

>

Hi, I'm new to D lang and encounter some performance issues with fiber, not sure if there's something obviously wrong with my code.

There is your problem.

>
auto scheduler = new FiberScheduler;

The Fiber scheduler will spawn a new fiber for every job.
It will not use a fiber pool. Spawning a new fiber is expensive because of the stack allocation for it.
Also if I recall correctly it will run single-threaded but I am not 100% sure on that.
Just have a look at the running processes ... if you just see one than you are single threaded.

I get 8->3 seconds using vibe's fiber scheduler, which still isn't competitive with Elixir.

--- primes.d	2021-07-24 21:37:46.633053839 -0500
+++ primesv1.d	2021-07-24 21:35:50.843053425 -0500
@@ -1,16 +1,19 @@
 /++ dub.sdl:
+    dependency "vibe-core" version="~>1.16.0"
  +/
-import std;
-import core.stdc.stdlib : exit;
+import std.stdio, std.conv;
+import vibe.core.concurrency;

 __gshared Tid mainTid;
 __gshared bool terminated = false;

 const int mailBoxSize = 1;

+extern(C) void _exit(int status);
+
 void main(string[] args) {
     auto n = args.length > 1 ? args[1].to!int() : 10;
-    auto scheduler = new FiberScheduler;
+    setConcurrencyPrimitive(ConcurrencyPrimitive.workerTask);
     scheduler.start({
         mainTid = thisTid();
         setMaxMailboxSize(mainTid, n, OnCrowding.throwException);
@@ -22,7 +25,7 @@
             writeln(prime);
         }
         terminated = true;
-        exit(0);
+        _exit(0);
     });
 }

build:

dub build --compiler=ldc -brelease --single primesv1.d

I think this is just a very goroutine-friendly test that relies on constantly spawning fibers and abusing message-passing rather than architecting out the concurrent parts of your program and how they should communicate. std.parallel's more appropriate in D.