Replies: 1 comment 1 reply
-
This is the same problem for any dispatcher if you do let a = spawn intToString(1)
let b = spawn sumThenFloat(1, 2) Your dispatcher needs to be able to handle both Type-erasureThe proper solution is type erasure however the basic continuation must not be tied to the GC.
Assuming mutant continuations the signatures are always in that vein type Continuation[T] = object
fn: proc (c: var Continuation[T]) {.nimcall.}
env: Envs Here are 2 solutions depending on the kind of application. 1. Compute and memory-bound dispatcherA dispatcher for compute application SHOULD NOT ever involve the Nim GC for it's low-level throughput sensitive operations. Inside the dispatcher you type erase all continuations to type ContinationBase = object
fn: proc (c: pointer){.nimcall.}
envs: UncheckedArray[byte] # or array[N, byte] with N a suitable max size Furthermore, the continuation can be made intrusive with other scheduler metadata:
And the scheduler will be able to manage it's own memory management scheme. 2. IO-bound schedulerFor an IO-bound scheduler where a GC is acceptable the raw continuation object can be wrapped. type ContinuationWrapper = ref object of RootObj
type ContinuationT = ref object of ContinuationWrapper
cont: proc (c: var Iterator){.nimcall.}
type ContinuationNS = ref object of ContinuationWrapper
cont: proc (c: var NetworkStream){.nimcall.} You get the same flexibility as directly using a ref object of RootObj 3. AdvantagesI don't see any inconvenient to have a plain object since plain object can always be wrapped in a ref object of RootObj.
This would be a killer Nim feature for the "no GC" constrained environments, especially The particular case of multithreadingLastly, for multithreading runtime using fork-join Parallelism
Submitting a child task is significantly easier as it is just a function call + parameters to serialize.
In short CPS is an tremendous opportunity for multithreading. Obviously all those memory and overhead advantages disappear if each ConclusionA plain object is more flexible and composable than a ref object of RootObj which would have no escape hatch. There are easy to implement solutions on plain object to fallback to Furthermore, core primitives of the language should be as much as possible usable without the GC, This would also suitably replace closures by something everyone could use instead of having to build their poor man's closure and trying to decipher |
Beta Was this translation helpful? Give feedback.
-
To be clear, we're talking about return values for continuation invocations inside the trampoline.
What we did originally was to return a continuation object. This was a
ref
. It held afn
pointing to the next call.The recent rewrite of
experiments/main.nim
looks like this:See also https://github.com/disruptek/cps/blob/master/experiments/eventqueue.nim
In this model, calls return a pointer to the next call and the continuation is mutated. The
fn
doesn't need to be stored in the continuation because it is returned by the call. This approach naturally lends itself to rewrite as a stack-based state machine.The deal-breaker, for me, is that my calls cannot return an arbitrary continuation type. This has pretty massive ramifications to composition, limiting what continuations can inject into whatever dispatcher they find themselves in.
Beta Was this translation helpful? Give feedback.
All reactions