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/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 d7f6049ce..a65929884 100644 --- a/bench/bench_nim.yaml +++ b/bench/bench_nim.yaml @@ -33,6 +33,8 @@ problems: source: - 1.nim - 3.nim + - 5.nim + - 6.nim - name: lru source: - 1.nim @@ -60,7 +62,9 @@ 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 + - 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 --passC=-ffp-contract=off --verbose after_build: - cp app out out_dir: out @@ -72,7 +76,9 @@ environments: include: nim include_sub_dir: before_build: - build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose + - 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 7ba786267..667c043c7 100644 --- a/bench/include/nim/app.nimble +++ b/bench/include/nim/app.nimble @@ -10,3 +10,5 @@ bin = @["app"] # Dependencies requires "nim >= 2.0.0" +requires "chronos" +requires "yasync"