Jump to page: 1 2
Thread overview
Performance issue with fiber
Jul 22
seany
Jul 25
jfondren
Jul 26
russhy
Jul 26
jfondren
Jul 28
drug
July 21

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
July 22

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.

July 24

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.

July 25

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.

July 26

Thank you for your response! I've got some questions tho.

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

>

It will not use a fiber pool.

Why fiber pool? Isn't fiber a lightweight logical thread which is already implemented with thread pool internally?

>

Spawning a new fiber is expensive because of the stack allocation for it.

Actually, I've benchmarked many stackful coroutine implementations in different langs but they're all much faster, and BTW, AFAIK go's goroutine is stackful as well (4KB IIRC)

July 26
>

build:

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


-brelease is a typo issue, i don't think that produce defired effect, most likely it defaulted to debug build

it should be -b release

July 26

On Monday, 26 July 2021 at 15:27:48 UTC, russhy wrote:

> >

build:

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


-brelease is a typo issue, i don't think that produce defired effect, most likely it defaulted to debug build

it should be -b release

No, it builds a release build with -brelease. Try it yourself, the first line of output tells you what the build type is. Instead of interpreting -abc as -a -b -c like getopt, dub interprets it as -a bc

--arch/-a works the same way, and although I don't see this usage in the official documentation, I didn't make it up:

https://duckduckgo.com/?q=dub+%22brelease%22&norw=1

July 28

On Monday, 26 July 2021 at 12:09:07 UTC, hanabi1224 wrote:

>

Thank you for your response! I've got some questions tho.

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

>

It will not use a fiber pool.

Why fiber pool? Isn't fiber a lightweight logical thread which is already implemented with thread pool internally?

Spawning fiber is expensive (but not so expensive as spawning thread, of course), but switching is fast.

Thus, you can spawn and pause "workers" fibers for avaiting of jobs.
(Probably, this behaviour is already implemented in number of libraries and it isn't actually need to implement another one.)

July 28

On Wednesday, 28 July 2021 at 01:12:16 UTC, Denis Feklushkin wrote:

>

Spawning fiber is expensive

Sorry but I cannot agree with the logic behind this statement, the whole point of using fiber is that, spwaning system thread is expensive, thus ppl create lightweight thread 'fiber', then if a 'fiber' is still considered expensive, should ppl invent 'giber' on top of 'fiber', then 'hiber' on top of 'giber'? That will be infinite.

If you mean 'each fiber has its own stack' by 'expensive', to be fair, golang's goroutine is stackful (4KB stack for each goroutine), it's like >50X faster with the same test case.

July 28
On 7/27/21 9:12 PM, Denis Feklushkin wrote:
> Spawning fiber is expensive (but not so expensive as spawning thread, of course), but switching is fast.
> 
> Thus, you can spawn and pause "workers" fibers for avaiting of jobs.
> (Probably, this behaviour is already implemented in number of libraries and it isn't actually need to implement another one.)

Agree with sibling comment by hanabi1224: spawning fiber (in other language runtimes) is INCREDIBLY fast, even though they have stack.

Something is wrong here.
« First   ‹ Prev
1 2