From dc63102ed54876f9ea41fa4eefae8652ea892b01 Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Sun, 13 Jul 2025 01:02:27 -0700 Subject: [PATCH 1/7] Add async solution to Nim --- bench/algorithm/coro-prime-sieve/3.nim | 93 ++++++++++++++++++++++++++ bench/bench_nim.yaml | 1 + 2 files changed, 94 insertions(+) create mode 100644 bench/algorithm/coro-prime-sieve/3.nim diff --git a/bench/algorithm/coro-prime-sieve/3.nim b/bench/algorithm/coro-prime-sieve/3.nim new file mode 100644 index 000000000..5f8a19cdd --- /dev/null +++ b/bench/algorithm/coro-prime-sieve/3.nim @@ -0,0 +1,93 @@ +import std/asyncdispatch +import std/os +import std/strutils + + + +## +## # Channel[T] +## +## As far as I know Nim's asyncdispatch has many features but lacks an awaitable +## channel type. It uses Futures as the work horse of async communication instead. +## +## I craeted a very simple async-awaitable channel type so I can match the +## reference Go implementation this benchmark originated from. +## + +type Channel[T] = object + ## An simple one-item, async-awaitable Channel. + untilIsEmpty: Future[void] + untilIsFull: Future[void] + val: T + + +proc newChannel[T](): ref Channel[T] = + ## Initializer. Allocate a new ref Channel object on the heap. + result = new Channel[T] + result[].untilIsEmpty = newFuture[void]() + result[].untilIsFull = newFuture[void]() + result[].untilIsEmpty.complete() + + +proc send[T](chan: ref Channel[T], val: T) {.async.} = + # Accept val if empty, otherwise, suspend until empty. + await chan[].untilIsEmpty + chan[].untilIsEmpty = newFuture[void]() + chan[].val = val + chan[].untilIsFull.complete() + + +proc recv[T](chan: ref Channel[T]): Future[T] {.async.} = + # Return held val if full, otherwise, suspend until full. + await chan[].untilIsFull + chan[].untilIsFull = newFuture[void]() + result = chan[].val + chan[].untilIsEmpty.complete() + + + +## +## # Benchmark +## +## Below, "Concurrent Prime Sieve" that matches Go reference implementation. +## +## [X] Uses coroutines. +## [X] Uses a coroutine scheduler. +## [X] Uses an async channel for communitating between coroutines. +## [X] Same 3 functions, structured like the reference. +## + + +proc generate(chan: ref Channel[int]) {.async.} = + ## Send the sequence 2, 3, 4, ... to cannel `chan`. + for i in 2 .. int.high: + await chan.send(i) + + +proc filter(inChan, outChan: ref Channel[int], prime: int) {.async.} = + ## Copy the values from channel `inChan` to channel `outChan`, removing those + ## divisible by `prime`. + while true: + let i = await inChan.recv() # revieve value from `inChan` + if i mod prime != 0: + await outChan.send(i) # send `i` to `outChan` + + +proc main(n: int) {.async.} = + ## The prime sieve: Daisy-chain filter processes. + var firstChan = newChannel[int]() # craete a new channel + asyncCheck generate(firstChan) # launch generate coroutine + for i in 0 ..< n: + let prime = await firstChan.recv() + echo prime + var secondChan = newChannel[int]() + asyncCheck filter(firstChan, secondChan, prime) + firstChan = secondChan + + +when isMainModule: + + let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100 + waitFor main(n) + + diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index 675bb8284..d7f6049ce 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -32,6 +32,7 @@ problems: - name: coro-prime-sieve source: - 1.nim + - 3.nim - name: lru source: - 1.nim From 463e9747557f45571437a14b4fc6bca698966ad0 Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Tue, 15 Jul 2025 09:19:28 -0700 Subject: [PATCH 2/7] Add another async solution to Nim --- bench/algorithm/coro-prime-sieve/4.nim | 91 ++++++++++++++++++++++++++ bench/bench_nim.yaml | 3 +- bench/include/nim/app.nimble | 1 + 3 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 bench/algorithm/coro-prime-sieve/4.nim diff --git a/bench/algorithm/coro-prime-sieve/4.nim b/bench/algorithm/coro-prime-sieve/4.nim new file mode 100644 index 000000000..3444cb1fe --- /dev/null +++ b/bench/algorithm/coro-prime-sieve/4.nim @@ -0,0 +1,91 @@ +import chronos +import std/os +import std/strutils + + + +## +## # Channel[T] +## +## I craeted a very simple async-awaitable channel type so I can match the +## reference Go implementation this benchmark originated from. +## + + +type Channel[T] = object + ## An simple one-item, async-awaitable Channel. + untilIsEmpty: Future[void] + untilIsFull: Future[void] + val: T + + +proc newChannel[T](): ref Channel[T] = + ## Initializer. Allocate a new ref Channel object on the heap. + result = new Channel[T] + result[].untilIsEmpty = newFuture[void]() + result[].untilIsFull = newFuture[void]() + result[].untilIsEmpty.complete() + + +proc send(chan: ref Channel[int], val: int) {.async.} = + # Accept val if empty, otherwise, suspend until empty. + await chan[].untilIsEmpty + chan[].untilIsEmpty = newFuture[void]() + chan[].val = val + chan[].untilIsFull.complete() + + +proc recv[T](chan: ref Channel[T]): Future[T] {.async.} = + # Return held val if full, otherwise, suspend until full. + await chan[].untilIsFull + chan[].untilIsFull = newFuture[void]() + result = chan[].val + chan[].untilIsEmpty.complete() + + + +## +## # Benchmark +## +## Below, "Concurrent Prime Sieve" that matches Go reference implementation. +## +## [X] Uses coroutines. +## [X] Uses a coroutine scheduler. +## [X] Uses an async channel for communitating between coroutines. +## [X] Same 3 functions, structured like the reference. +## + + +proc generate(chan: ref Channel[int]) {.async.} = + ## Send the sequence 2, 3, 4, ... to cannel `chan`. + for i in 2 .. int.high: + await chan.send(i) + + +proc filter(inChan, outChan: ref Channel[int], prime: int) {.async.} = + ## Copy the values from channel `inChan` to channel `outChan`, removing those + ## divisible by `prime`. + while true: + let i = await inChan.recv() # revieve value from `inChan` + if i mod prime != 0: + await outChan.send(i) # send `i` to `outChan` + + +proc main(n: int) {.async.} = + ## The prime sieve: Daisy-chain filter processes. + var firstChan = newChannel[int]() # craete a new channel + asyncCheck generate(firstChan) # launch generate coroutine + for i in 0 ..< n: + let prime = await firstChan.recv() + echo prime + var secondChan = newChannel[int]() + asyncCheck filter(firstChan, secondChan, prime) + firstChan = secondChan + + +when isMainModule: + + let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100 + waitFor main(n) + + diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index d7f6049ce..e75d60fb1 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -33,6 +33,7 @@ problems: source: - 1.nim - 3.nim + - 4.nim - name: lru source: - 1.nim @@ -60,7 +61,7 @@ environments: include: nim include_sub_dir: before_build: - build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose + build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC="-march=native -mtune=native -flto -fwhole-program" --verbose after_build: - cp app out out_dir: out diff --git a/bench/include/nim/app.nimble b/bench/include/nim/app.nimble index 7ba786267..a337ba2b4 100644 --- a/bench/include/nim/app.nimble +++ b/bench/include/nim/app.nimble @@ -10,3 +10,4 @@ bin = @["app"] # Dependencies requires "nim >= 2.0.0" +requires "chronos" From a34cc259e0ba2c8e85b6722e878dc2371789eaed Mon Sep 17 00:00:00 2001 From: Clemente90 Date: Tue, 15 Jul 2025 16:51:27 -0700 Subject: [PATCH 3/7] Update bench/bench_nim.yaml Co-authored-by: hanabi1224 --- bench/bench_nim.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index e75d60fb1..c49d7d2e6 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -61,7 +61,7 @@ environments: include: nim include_sub_dir: before_build: - build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC="-march=native -mtune=native -flto -fwhole-program" --verbose + build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC="-march=broadwell -flto -fwhole-program" --verbose after_build: - cp app out out_dir: out From b9e86935f997a4920116befdb0ab846e2d018ffc Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Thu, 17 Jul 2025 14:09:47 -0700 Subject: [PATCH 4/7] Revert build line --- bench/bench_nim.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index e75d60fb1..e7efe82f2 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -61,7 +61,7 @@ environments: include: nim include_sub_dir: before_build: - build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC="-march=native -mtune=native -flto -fwhole-program" --verbose + build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose after_build: - cp app out out_dir: out From 6863f1ce9c146cd68875a448c1e34ecef2c5ac95 Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Fri, 18 Jul 2025 08:45:15 -0700 Subject: [PATCH 5/7] Add command to install deps prior to build --- bench/bench_nim.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index e7efe82f2..5c3b9d87b 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -60,7 +60,7 @@ environments: # docker: nimlang/nim include: nim include_sub_dir: - before_build: + before_build: nimble install --depsOnly build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose after_build: - cp app out @@ -72,7 +72,7 @@ environments: # docker: nimlang/nim include: nim include_sub_dir: - before_build: + before_build: nimble install --depsOnly build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose after_build: - cp app out From a8831c9c5691c291b83e046fa8a99ca161ef45b1 Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Mon, 28 Jul 2025 16:04:33 -0700 Subject: [PATCH 6/7] fix build and add new implementations --- bench/algorithm/coro-prime-sieve/5.nim | 43 +++++++++++++ bench/algorithm/coro-prime-sieve/6.nim | 83 ++++++++++++++++++++++++++ bench/bench_nim.yaml | 14 +++-- bench/include/nim/app.nimble | 1 + 4 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 bench/algorithm/coro-prime-sieve/5.nim create mode 100644 bench/algorithm/coro-prime-sieve/6.nim diff --git a/bench/algorithm/coro-prime-sieve/5.nim b/bench/algorithm/coro-prime-sieve/5.nim new file mode 100644 index 000000000..38a922c2a --- /dev/null +++ b/bench/algorithm/coro-prime-sieve/5.nim @@ -0,0 +1,43 @@ +import chronos +import std/os +import std/strutils + + + + + + + +proc generate(chan: AsyncQueue[int]) {.async.} = + ## Send the sequence 2, 3, 4, ... to cannel `chan`. + for i in 2 .. int.high: + await chan.addFirst(i) + + +proc filter(inChan, outChan: AsyncQueue[int], prime: int) {.async.} = + ## Copy the values from channel `inChan` to channel `outChan`, removing those + ## divisible by `prime`. + while true: + let i = await inChan.popLast() # revieve value from `inChan` + if i mod prime != 0: + await outChan.addFirst(i) # send `i` to `outChan` + + +proc main(n: int) {.async.} = + ## The prime sieve: Daisy-chain filter processes. + var firstChan = newAsyncQueue[int](1) # craete a new channel + asyncCheck generate(firstChan) # launch generate coroutine + for i in 0 ..< n: + let prime = await firstChan.popLast() + echo prime + var secondChan = newAsyncQueue[int](1) + asyncCheck filter(firstChan, secondChan, prime) + firstChan = secondChan + + +when isMainModule: + + let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100 + waitFor main(n) + + diff --git a/bench/algorithm/coro-prime-sieve/6.nim b/bench/algorithm/coro-prime-sieve/6.nim new file mode 100644 index 000000000..53b16c352 --- /dev/null +++ b/bench/algorithm/coro-prime-sieve/6.nim @@ -0,0 +1,83 @@ +import yasync +import chronos +import std/os +import std/strutils + + +## +## Channel implementation copied from the `yasync` repo, testcase `test7.nim` +## + +type + Channel[T] = object + waitingCont: ptr Cont[T] + sendingCont: ptr Cont[void] + val: T + +proc send[T](c: var Channel[T], v: T, env: ptr Cont[void]) {.asyncRaw.} = + doAssert(c.sendingCont == nil, "Too many senders") + if c.waitingCont == nil: + c.val = v + c.sendingCont = env + else: + let cont = c.waitingCont + c.waitingCont = nil + cont.complete(v) + env.complete() + +proc recv[T](c: var Channel[T], env: ptr Cont[T]) {.asyncRaw.} = + doAssert(c.waitingCont == nil, "Too many receivers") + if c.sendingCont == nil: + c.waitingCont = env + else: + let cont = c.sendingCont + c.sendingCont = nil + env.complete(c.val) + cont.complete() + +proc newChannel[T](): ref Channel[T] = + new(result) + + +## +## # Benchmark +## +## Below, "Concurrent Prime Sieve" that matches Go reference implementation. +## +## [X] Uses coroutines. +## [X] Uses a coroutine scheduler. +## [X] Uses an async channel for communitating between coroutines. +## [X] Same 3 functions, structured like the reference. +## + +proc generate(chan: ref Channel[int]) {.yasync.async.} = + ## Send the sequence 2, 3, 4, ... to cannel `chan`. + for i in 2 .. int.high: + await chan[].send(i) + +proc filter(inChan, outChan: ref Channel[int], prime: int) {.yasync.async.} = + ## Copy the values from channel `inChan` to channel `outChan`, removing those + ## divisible by `prime`. + while true: + let i = await inChan[].recv() # revieve value from `inChan` + if i mod prime != 0: + await outChan[].send(i) # send `i` to `outChan` + +proc main(n: int) {.yasync.async.} = + ## The prime sieve: Daisy-chain filter processes. + var firstChan = newChannel[int]() # craete a new channel + discard generate(firstChan) # launch generate coroutine + for i in 0 ..< n: + let prime = await firstChan[].recv() + echo prime + var secondChan = newChannel[int]() + discard filter(firstChan, secondChan, prime) + firstChan = secondChan + +when isMainModule: + + let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100 + var env: asyncCallEnvType(main(n)) + asyncLaunchWithEnv(env, main(n)) + + diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index 5c3b9d87b..8a385a0eb 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -34,6 +34,8 @@ problems: - 1.nim - 3.nim - 4.nim + - 5.nim + - 6.nim - name: lru source: - 1.nim @@ -60,8 +62,10 @@ environments: # docker: nimlang/nim include: nim include_sub_dir: - before_build: nimble install --depsOnly - build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose + before_build: + - nimble refresh + - nimble install --depsOnly + build: nimble cpp app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC=-march=native --passC=-mtune=native --passC=-flto --passC=-fwhole-program --verbose after_build: - cp app out out_dir: out @@ -72,8 +76,10 @@ environments: # docker: nimlang/nim include: nim include_sub_dir: - before_build: nimble install --depsOnly - build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose + before_build: + - nimble refresh + - nimble install --depsOnly + build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose after_build: - cp app out out_dir: out diff --git a/bench/include/nim/app.nimble b/bench/include/nim/app.nimble index a337ba2b4..667c043c7 100644 --- a/bench/include/nim/app.nimble +++ b/bench/include/nim/app.nimble @@ -11,3 +11,4 @@ bin = @["app"] requires "nim >= 2.0.0" requires "chronos" +requires "yasync" From 46fba96d73cedbf417cde7351e1d21c22be0e1ba Mon Sep 17 00:00:00 2001 From: Kevin Lewis Date: Tue, 29 Jul 2025 23:21:27 -0700 Subject: [PATCH 7/7] fix 'floating point contraction' messing up mandelbrot --- bench/bench_nim.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bench/bench_nim.yaml b/bench/bench_nim.yaml index 8a385a0eb..a65929884 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -33,7 +33,6 @@ problems: source: - 1.nim - 3.nim - - 4.nim - 5.nim - 6.nim - name: lru @@ -65,7 +64,7 @@ environments: before_build: - nimble refresh - nimble install --depsOnly - build: nimble cpp app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC=-march=native --passC=-mtune=native --passC=-flto --passC=-fwhole-program --verbose + build: nimble cpp app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --passC=-march=native --passC=-mtune=native --passC=-flto --passC=-fwhole-program --passC=-ffp-contract=off --verbose after_build: - cp app out out_dir: out