-
Notifications
You must be signed in to change notification settings - Fork 988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request: Nestable Engines #702
Comments
I narrowed down the slowness to the
|
Some benchmarks:
So the overhead of the implementation of nestable engines above is huge (over a 7x slowdown), and I am wondering how easy it would be to modify the current implementation of engines in ChezScheme to be nestable. Thanks! |
FWIW I think, this is the place too look: |
Indeed, and here is where a nested engine causes an explicit error: |
I got working something similar, I can send you a stub privately. |
Sure, if I can make it public eventually (and I’d rather you send it publically for the scrutiny): [email protected] thanks! |
I've not done much with the engines implementation, but I ran across a paper Engines from Continuations that Kent Dybvig and Robert Hieb wrote about the implementation back in 1988. The article notes that nested engines using continuations leads to the nested continuation capturing the state of the engine it is nested in, and propose an alternative implementation with a slightly modified API to allow it to allow for continuation-based implementation. |
Thanks, @akeep! The code from the appendix works! I was able to get my application working with minimal overhead. I had to define the following functions from the paper interface:
I think I need to do something a bit more complicated than that. I am currently getting an error like "Exception in read: not permitted on closed port #<input port ...>" when re-running tests multiple times. It seems like I might need to setup and cleanup like s/engine.ss does, but this is looking promising. |
For reference, here is the code: (define (stop-timer) (set-timer 0))
(define (start-timer ticks new-handler)
(timer-interrupt-handler new-handler)
(set-timer ticks))
(define make-full-engine)
(letrec
([new-engine
(lambda (proc id)
(lambda (ticks return expire)
((call/cc
(lambda (k)
(run proc
(stop-timer)
ticks
(lambda (value ticks engine-maker)
(k (lambda () (return value ticks engine-maker))))
(lambda (engine)
(k (lambda () (expire engine))))
id))))))]
[run
(lambda (resume parent child return expire id)
(let ([ticks (if (and (active?) (< parent child)) parent child)])
(push (- parent ticks) (- child ticks) return expire id)
(resume ticks)))]
[go
(lambda (ticks)
(when (active?)
(if (= ticks 0)
(timer-handler)
(start-timer ticks timer-handler))))]
[do-return
(lambda (proc value ticks id1)
(pop (lambda (parent child return expire id2)
(if (eq? id1 id2)
(begin (go (+ parent ticks))
(return value
(+ child ticks)
(lambda (value) (new-engine (proc value) id1))))
(do-return
(lambda (value)
(lambda (new-ticks)
(run (proc value) new-ticks (+ child ticks) return expire id2)))
value
(+ parent ticks)
id1)))))]
[do-expire
(lambda (resume)
(pop (lambda (parent child return expire id)
(if (> child 0)
(do-expire (lambda (ticks) (run resume ticks child return expire id)))
(begin (go parent)
(expire (new-engine resume id)))))))]
[timer-handler (lambda () (go (call/cc do-expire)))]
[stack '()]
[push (lambda l (set! stack (cons l stack)))]
[pop
(lambda (handler)
(if (null? stack)
(error 'engine "attempt to return from inactive engine")
(let ([top (car stack)])
(set! stack (cdr stack))
(apply handler top))))]
[active? (lambda () (not (null? stack)))])
(set! make-full-engine
(lambda (proc)
(letrec ([engine-return
(lambda (value)
(call/cc
(lambda (k)
(do-return (lambda (value)
(lambda (ticks)
(go ticks)
(k value)))
value
(stop-timer)
engine-return))))])
(new-engine (lambda (ticks)
(go ticks)
(proc engine-return)
(error 'engine "invalid completion"))
engine-return)))))
(define make-engine
(letrec ([simplify (lambda (engine)
(lambda (ticks return expire)
(engine ticks
(lambda (value ticks engine-maker)
(return ticks value))
(lambda (engine)
(expire (simplify engine))))))])
(lambda (proc)
(simplify (make-full-engine (lambda (ret) (ret (proc)))))))) |
Hmm, setting up and cleaning up like s/engine.ss does seems a bit involved. I am also not sure about the semantics when the timer handler is overwritten by a nested engine. This doesn't seem to be an issue in the paper. To be continued... any advice appreciated. |
I found that my bug was because the engine was returning while the timer was not at 0, and later when it becomes 0, it calls the obsolete handler of the returned engine. I fix this bug by setting the timer to 0 in in the simplified (define make-engine
(letrec ([simplify (lambda (engine)
(lambda (ticks return expire)
(engine ticks
(lambda (value ticks engine-maker)
;; added: stop timer,
;; to avoid firing after engine completes
(stop-timer)
(return ticks value))
(lambda (engine)
(expire (simplify engine))))))])
(lambda (proc)
(simplify (make-full-engine (lambda (ret) (ret (proc)))))))) If anyone has thoughts on cleaning up like in s/engine.ss that would be appreciated. I am not sure what are the consequences of not doing so. Thanks! |
Hmm, unfortunately, the fix seems wrong. For:
We get |
As an update, the root cause of the engines misbehaving (firing at random times, once they're obsolete) seems to be related to exceptions occurring within engines. So cleaning up like Thanks. |
OK, the code below seems to work with exception-throwing engines. ;; from Appendix A of https://legacy.cs.indiana.edu/~dyb/pubs/engines.pdf
(define (stop-timer) (set-timer 0))
(define (start-timer ticks new-handler)
(timer-interrupt-handler new-handler)
(set-timer ticks))
(define make-engine)
(letrec
([new-engine
(lambda (proc id)
(lambda (ticks return expire)
((call/cc
(lambda (k)
(run proc
(stop-timer)
ticks
(lambda (value ticks engine-maker)
(k (lambda () (return value ticks engine-maker))))
(lambda (engine)
(k (lambda () (expire engine))))
id))))))]
[run
(lambda (resume parent child return expire id)
(let ([ticks (if (and (active?) (< parent child)) parent child)])
(push (- parent ticks) (- child ticks) return expire id)
(resume ticks)))]
[go
(lambda (ticks)
(when (active?)
(if (= ticks 0)
(timer-handler)
(start-timer ticks timer-handler))))]
[do-return
(lambda (proc value ticks id1)
(pop (lambda (parent child return expire id2)
(if (eq? id1 id2)
(begin (go (+ parent ticks))
(return value
(+ child ticks)
(lambda (value) (new-engine (proc value) id1))))
(do-return
(lambda (value)
(lambda (new-ticks)
(run (proc value) new-ticks (+ child ticks) return expire id2)))
value
(+ parent ticks)
id1)))))]
[do-expire
(lambda (resume)
(pop (lambda (parent child return expire id)
(if (> child 0)
(do-expire (lambda (ticks) (run resume ticks child return expire id)))
(begin (go parent)
(expire (new-engine resume id)))))))]
[do-raise
(lambda (c ticks id1)
(pop (lambda (parent child return expire id2)
(if (eq? id1 id2)
(begin (go (+ parent ticks))
(raise-continuable c))
(do-raise c (+ parent ticks) id1)))))]
[timer-handler (lambda () (go (call/cc do-expire)))]
[stack '()]
[push (lambda l (set! stack (cons l stack)))]
[pop
(lambda (handler)
(if (null? stack)
(error 'engine "attempt to return from inactive engine")
(let ([top (car stack)])
(set! stack (cdr stack))
(apply handler top))))]
[active? (lambda () (not (null? stack)))]
[make-full-engine
(lambda (proc)
(letrec ([engine-return
(lambda (value)
(call/cc
(lambda (k)
(do-return (lambda (value)
(lambda (ticks)
(go ticks)
(k value)))
value
(stop-timer)
engine-return))))])
(new-engine (lambda (ticks)
(go ticks)
(with-exception-handler
(lambda (c)
(do-raise c (stop-timer) engine-return))
(lambda ()
(proc engine-return)))
(error 'engine "invalid completion"))
engine-return)))])
(set! make-engine
(letrec ([simplify (lambda (engine)
(lambda (ticks return expire)
(engine ticks
(lambda (value ticks engine-maker)
(return ticks value))
(lambda (engine)
(expire (simplify engine))))))])
(lambda (proc)
(simplify (make-full-engine (lambda (ret) (ret (proc))))))))) |
How difficult would it be to adapt the code from s/engine.ss to support nestable engines?
I have code that supports nestable engines (see below), but it requires a custom
timed-lambda
and redefininglambda
totimed-lambda
results in a big slowdown.Note that the slowdown occurs even when engines are not otherwise used.
Would it be possible to use the timer mechanism in a nested fashion?
Thanks for any advice.
The text was updated successfully, but these errors were encountered: