Hello
While playing with streams, I think I've noticed that the stream "head" is not garbage collected while traversing the stream even if it is not referenced by user data structures anymore.
There also seems to be a (independent?) problem releasing memory when streams are long (thus recursion is deep). I'm getting out of memory errors when "there shouldn't be", and need to run (##gc) manually to be able to continue calculations (entering ,c in the debugger resumes calculation correctly): it looks like gambit is starting to fill up all available memory if the recursion depth is bigger than something between 20000 and 35000.
The two issues happen in both interpreted and compiled code.
$ ulimit -S -v 300000 $ gsc
; (compile-file "test.scm") (load "test") (test 35000)
repeat the test call several times, it will give *** ERROR IN ##bignum.make -- Heap overflow after about 10 invocations. Same thing with
(test-2 35000)
This can be repeated "forever" without hitting out of memory errors:
(test 20000)
or this
(test-2 20000)
(There are two implementations of stream-take below (one commented out), choosing the other one doesn't change the situation.)
This is with Gambit Version 4.0 beta 14 on Debian ppc. With beta 15 on Debian x86, the out of memory situation already happens with a test argument of 25000.
What I'm especially wondering is this: the output list from take only needs about 600000 bytes. So if the stream would be deallocated while stream-take is still walking it, much longer streams should be possible (at least 10 times longer). Do you have an explanation or a solution?
Thanks Christian.
;; test.scm
(declare (block) (standard-bindings) (extended-bindings) )
;; stream library
(define *stream-strict* #f) (set! *stream-strict* #t)
(define @force ##force) (define @promise? ##promise?) (define @car ##car) (define @cdr ##cdr) (define @pair? ##pair?) (define @null? ##null?)
(define (stream-force strm) (if (@promise? strm) (@force strm) (if *stream-strict* (error "not a stream: expected promise, got:" strm) strm)))
(define stream-car (lambda (strm) (let ((p (stream-force strm))) (if (@pair? p) (@car p) (error "stream-car: not a pair:" p))))) (define stream-cdr (lambda (strm) (let ((p (stream-force strm))) (if (@pair? p) (@cdr p) (error "stream-cdr: not a pair:" p)))))
(define (null-stream? strm) (let ((l (stream-force strm))) (cond ((##pair? l) #f) ((##null? l) #t) (else (error "null-stream?: argument out of domain:" l)))))
; (define (stream-take strm k) ;; returns a list, not a stream. ; (if (and (##fixnum? k) (##fixnum.>= k 0)) ; (if (##fixnum.= k 0) ; '() ; (or (and (not (null-stream? strm)) ; (let ((res (cons (stream-car strm) '()))) ; (let iter ((res-tail res) ; (strm (stream-cdr strm)) ; (k (##fixnum.- k 1))) ; (if (##fixnum.zero? k) res ; (let ((p (stream-force strm))) ; (and (##pair? p) ; (let ((res-next (##cons (##car p) '()))) ; (##set-cdr! res-tail res-next) ; (iter res-next ; (##cdr p) ; (##fixnum.- k 1))))))))) ; (error "stream-take: stream too short"))) ; (error "stream-take: k is not a positive fixnum:" k)))
(define (stream-take strm k) ;; returns a list, not a stream. (if (and (##fixnum? k) (##fixnum.>= k 0)) (let recur ((strm strm) (k k)) (if (##fixnum.zero? k) '() (let ((p (stream-force strm))) (if (##pair? p) (##cons (##car p) (recur (##cdr p) (##fixnum.- k 1))) (error "stream-take: stream too short"))))) (error "stream-take: k is not a positive fixnum:" k)))
(define-macro (scons a r) `(delay (cons ,a ,r)))
;; the actual program
(define (fib-stream) (letrec ((strm (scons 0 (scons 1 (let recur ((2behind (let ((s strm)) ;(set! strm #f); make gc possible? no, doesn't help. s))) (delay (let ((a (stream-car 2behind)) (r (stream-cdr 2behind)) ) (cons (+ a (stream-car r)) (recur r))))))))) strm))
(define (fib-stream-2) ;; (misses the first two values of the fib series) (let recur ((pprev 0) (prev 1)) (delay (let ((cur (+ prev pprev))) (cons cur (recur prev cur))))))
(define t #f)
(define (test n) (time (set! t (stream-take (fib-stream) n))))
(define (test-2 n) (time (set! t (stream-take (fib-stream-2) n))))
;;end
Afficher les réponses par date
At 22:49 Uhr +0100 30.10.2005, I wrote:
While playing with streams, I think I've noticed that the stream "head" is not garbage collected while traversing the stream even if it is not referenced by user data structures anymore.
Sorry, please accept my apology. I was completely blind to the fact that the bignums in the stream (and also in the output list from stream-take) were huge, and so the conclusion that "the output list from take only needs about 600000 bytes" (as measured by (time (list-copy t))) is of course wrong. With (declare (fixnum)(not safe)) the "issue" is gone. Also using stream-ref (see below) instead of stream-take, Gambit clearly is showing to be deallocating the stream head. (The other observation, that Gambit is seemingly increasingly filling up memory after *repeated* runs with bignums and large n's in the example of my last mail, might still be an issue, but I would have to experiment more to be sure.)
Thanks for this nice scheme system Christian.
;; -- 8< --- (the mentioned stream-ref function: )
(define (stream-drop strm k) (if (and (##fixnum? k) (##fixnum.>= k 0)) (let iter ((strm strm) (k k)) (if (##fixnum.zero? k) strm (let ((p (stream-force strm))) (if (##pair? p) (iter (##cdr p) (##fixnum.- k 1)) (error "stream-drop: stream too short"))))) (error "stream-drop: k is not a positive fixnum:" k)))
(define (stream-ref strm i) (stream-car (stream-drop strm i)))
;; test with: (time (stream-ref (fib-stream) 350000)) ;; (takes a long time, but does not take up memory)