Skip to content

Commit ad18b20

Browse files
authored
Chronos coro prime sieve (#483)
1 parent 3fb8f97 commit ad18b20

File tree

5 files changed

+227
-2
lines changed

5 files changed

+227
-2
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import chronos
2+
import std/os
3+
import std/strutils
4+
5+
6+
7+
##
8+
## # Channel[T]
9+
##
10+
## I craeted a very simple async-awaitable channel type so I can match the
11+
## reference Go implementation this benchmark originated from.
12+
##
13+
14+
15+
type Channel[T] = object
16+
## An simple one-item, async-awaitable Channel.
17+
untilIsEmpty: Future[void]
18+
untilIsFull: Future[void]
19+
val: T
20+
21+
22+
proc newChannel[T](): ref Channel[T] =
23+
## Initializer. Allocate a new ref Channel object on the heap.
24+
result = new Channel[T]
25+
result[].untilIsEmpty = newFuture[void]()
26+
result[].untilIsFull = newFuture[void]()
27+
result[].untilIsEmpty.complete()
28+
29+
30+
proc send(chan: ref Channel[int], val: int) {.async.} =
31+
# Accept val if empty, otherwise, suspend until empty.
32+
await chan[].untilIsEmpty
33+
chan[].untilIsEmpty = newFuture[void]()
34+
chan[].val = val
35+
chan[].untilIsFull.complete()
36+
37+
38+
proc recv[T](chan: ref Channel[T]): Future[T] {.async.} =
39+
# Return held val if full, otherwise, suspend until full.
40+
await chan[].untilIsFull
41+
chan[].untilIsFull = newFuture[void]()
42+
result = chan[].val
43+
chan[].untilIsEmpty.complete()
44+
45+
46+
47+
##
48+
## # Benchmark
49+
##
50+
## Below, "Concurrent Prime Sieve" that matches Go reference implementation.
51+
##
52+
## [X] Uses coroutines.
53+
## [X] Uses a coroutine scheduler.
54+
## [X] Uses an async channel for communitating between coroutines.
55+
## [X] Same 3 functions, structured like the reference.
56+
##
57+
58+
59+
proc generate(chan: ref Channel[int]) {.async.} =
60+
## Send the sequence 2, 3, 4, ... to cannel `chan`.
61+
for i in 2 .. int.high:
62+
await chan.send(i)
63+
64+
65+
proc filter(inChan, outChan: ref Channel[int], prime: int) {.async.} =
66+
## Copy the values from channel `inChan` to channel `outChan`, removing those
67+
## divisible by `prime`.
68+
while true:
69+
let i = await inChan.recv() # revieve value from `inChan`
70+
if i mod prime != 0:
71+
await outChan.send(i) # send `i` to `outChan`
72+
73+
74+
proc main(n: int) {.async.} =
75+
## The prime sieve: Daisy-chain filter processes.
76+
var firstChan = newChannel[int]() # craete a new channel
77+
asyncCheck generate(firstChan) # launch generate coroutine
78+
for i in 0 ..< n:
79+
let prime = await firstChan.recv()
80+
echo prime
81+
var secondChan = newChannel[int]()
82+
asyncCheck filter(firstChan, secondChan, prime)
83+
firstChan = secondChan
84+
85+
86+
when isMainModule:
87+
88+
let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100
89+
waitFor main(n)
90+
91+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import chronos
2+
import std/os
3+
import std/strutils
4+
5+
6+
7+
8+
9+
10+
11+
proc generate(chan: AsyncQueue[int]) {.async.} =
12+
## Send the sequence 2, 3, 4, ... to cannel `chan`.
13+
for i in 2 .. int.high:
14+
await chan.addFirst(i)
15+
16+
17+
proc filter(inChan, outChan: AsyncQueue[int], prime: int) {.async.} =
18+
## Copy the values from channel `inChan` to channel `outChan`, removing those
19+
## divisible by `prime`.
20+
while true:
21+
let i = await inChan.popLast() # revieve value from `inChan`
22+
if i mod prime != 0:
23+
await outChan.addFirst(i) # send `i` to `outChan`
24+
25+
26+
proc main(n: int) {.async.} =
27+
## The prime sieve: Daisy-chain filter processes.
28+
var firstChan = newAsyncQueue[int](1) # craete a new channel
29+
asyncCheck generate(firstChan) # launch generate coroutine
30+
for i in 0 ..< n:
31+
let prime = await firstChan.popLast()
32+
echo prime
33+
var secondChan = newAsyncQueue[int](1)
34+
asyncCheck filter(firstChan, secondChan, prime)
35+
firstChan = secondChan
36+
37+
38+
when isMainModule:
39+
40+
let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100
41+
waitFor main(n)
42+
43+
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import yasync
2+
import chronos
3+
import std/os
4+
import std/strutils
5+
6+
7+
##
8+
## Channel implementation copied from the `yasync` repo, testcase `test7.nim`
9+
##
10+
11+
type
12+
Channel[T] = object
13+
waitingCont: ptr Cont[T]
14+
sendingCont: ptr Cont[void]
15+
val: T
16+
17+
proc send[T](c: var Channel[T], v: T, env: ptr Cont[void]) {.asyncRaw.} =
18+
doAssert(c.sendingCont == nil, "Too many senders")
19+
if c.waitingCont == nil:
20+
c.val = v
21+
c.sendingCont = env
22+
else:
23+
let cont = c.waitingCont
24+
c.waitingCont = nil
25+
cont.complete(v)
26+
env.complete()
27+
28+
proc recv[T](c: var Channel[T], env: ptr Cont[T]) {.asyncRaw.} =
29+
doAssert(c.waitingCont == nil, "Too many receivers")
30+
if c.sendingCont == nil:
31+
c.waitingCont = env
32+
else:
33+
let cont = c.sendingCont
34+
c.sendingCont = nil
35+
env.complete(c.val)
36+
cont.complete()
37+
38+
proc newChannel[T](): ref Channel[T] =
39+
new(result)
40+
41+
42+
##
43+
## # Benchmark
44+
##
45+
## Below, "Concurrent Prime Sieve" that matches Go reference implementation.
46+
##
47+
## [X] Uses coroutines.
48+
## [X] Uses a coroutine scheduler.
49+
## [X] Uses an async channel for communitating between coroutines.
50+
## [X] Same 3 functions, structured like the reference.
51+
##
52+
53+
proc generate(chan: ref Channel[int]) {.yasync.async.} =
54+
## Send the sequence 2, 3, 4, ... to cannel `chan`.
55+
for i in 2 .. int.high:
56+
await chan[].send(i)
57+
58+
proc filter(inChan, outChan: ref Channel[int], prime: int) {.yasync.async.} =
59+
## Copy the values from channel `inChan` to channel `outChan`, removing those
60+
## divisible by `prime`.
61+
while true:
62+
let i = await inChan[].recv() # revieve value from `inChan`
63+
if i mod prime != 0:
64+
await outChan[].send(i) # send `i` to `outChan`
65+
66+
proc main(n: int) {.yasync.async.} =
67+
## The prime sieve: Daisy-chain filter processes.
68+
var firstChan = newChannel[int]() # craete a new channel
69+
discard generate(firstChan) # launch generate coroutine
70+
for i in 0 ..< n:
71+
let prime = await firstChan[].recv()
72+
echo prime
73+
var secondChan = newChannel[int]()
74+
discard filter(firstChan, secondChan, prime)
75+
firstChan = secondChan
76+
77+
when isMainModule:
78+
79+
let n = if paramCount() > 0: parseInt(paramStr(1)) else: 100
80+
var env: asyncCallEnvType(main(n))
81+
asyncLaunchWithEnv(env, main(n))
82+
83+

bench/bench_nim.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ problems:
3333
source:
3434
- 1.nim
3535
- 3.nim
36+
- 5.nim
37+
- 6.nim
3638
- name: lru
3739
source:
3840
- 1.nim
@@ -60,7 +62,9 @@ environments:
6062
include: nim
6163
include_sub_dir:
6264
before_build:
63-
build: nimble build app -y --mm:orc -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose
65+
- nimble refresh
66+
- nimble install --depsOnly
67+
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
6468
after_build:
6569
- cp app out
6670
out_dir: out
@@ -72,7 +76,9 @@ environments:
7276
include: nim
7377
include_sub_dir:
7478
before_build:
75-
build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose
79+
- nimble refresh
80+
- nimble install --depsOnly
81+
build: nimble build app -y --mm:orc --cc:clang -d:danger --panics:on -d:nimCoroutines --threads:on --tlsEmulation:off --verbose
7682
after_build:
7783
- cp app out
7884
out_dir: out

bench/include/nim/app.nimble

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ bin = @["app"]
1010
# Dependencies
1111

1212
requires "nim >= 2.0.0"
13+
requires "chronos"
14+
requires "yasync"

0 commit comments

Comments
 (0)