good morning;
i have noticed that a long-running sbcl accumulates "unreferenced"
dynamic objects.
the immediate symptom is that entries remain in weak-keyed hash
tables for which the only initial reference was a term in an
anonymous lambda expression.
if i try something obvious like
(loop (let ((x (let ((object (copy-seq "asdf")))
(setf (literal-language-tag object) "EX")
(compile nil `(lambda (x)
,@(loop for i from 0 below 1000000
collect (list 'incf 'x))
x)))))
(unless (typep x 'function) (return))))
after a short time, an image looks like
* (sb-ext:gc)
NIL
* (room)
Dynamic space usage is: 145,221,152 bytes.
Read-only space usage is: 3,504 bytes.
Static space usage is: 2,832 bytes.
Control stack usage is: 1,208 bytes.
Binding stack usage is: 344 bytes.
Control and binding stack usage is for the current thread only.
Garbage collection is currently enabled.
Breakdown for dynamic space:
64,998,080 bytes for 1,383,921 instance objects.
26,525,336 bytes for 3,315,667 cons objects.
18,381,728 bytes for 196,759 simple-vector objects.
17,850,176 bytes for 26,719 code objects.
18,087,184 bytes for 437,334 other objects.
145,842,504 bytes for 5,360,400 dynamic objects (space total.)
*
if i compile functions each with a "fingerprinted" constituent form,
_various_ of the forms are retained:
* (let ((sym (gensym)))
(dotimes (x 10) (compile nil `(lambda (x) (equal x '(,sym ,x)))))
(dotimes (i 3) (sb-ext:gc))
(let ((found nil))
(dotimes (x 10)
(let ((list `(,sym ,x)))
(sb-vm::map-allocated-objects #'(lambda (object code size)
(declare (ignore code size))
(when (and (equal object
list) (not (eq object list)))
(push object found)))
:dynamic)))
found))
((#:G622 9) (#:G622 8) (#:G622 7) (#:G622 6) (#:G622 4) (#:G622 3)
(#:G622 2)
(#:G622 1) (#:G622 0))
* (let ((sym (gensym)))
(dotimes (x 10) (compile nil `(lambda (x) (equal x '(,sym ,x)))))
(dotimes (i 10) (sb-ext:gc))
(let ((found nil))
(dotimes (x 10)
(let ((list `(,sym ,x)))
(sb-vm::map-allocated-objects #'(lambda (object code size)
(declare (ignore code size))
(when (and (equal object
list) (not (eq object list)))
(push object found)))
:dynamic)))
found))
((#:G623 9) (#:G623 8) (#:G623 7) (#:G623 6) (#:G623 5) (#:G623 4)
(#:G623 3)
(#:G623 1) (#:G623 0))
*
if i change the report, the result differ:
* (let ((sym (gensym)))
(dotimes (x 10) (compile nil `(lambda (x) (equal x '(,sym ,x)))))
(dotimes (i 10) (sb-ext:gc))
(let ((found 0))
(dotimes (x 10)
(let ((list `(,sym ,x)))
(sb-vm::map-allocated-objects #'(lambda (object code size)
(declare (ignore code size))
(when (and (equal object
list) (not (eq object list)))
(print (cons x (incf
found)))
(sb-vm::map-referencing-
objects #'print :dynamic object)))
:dynamic)))
found))
(0 . 1)
((#:G627 0))
#(#<HASH-TABLE :TEST EQ :COUNT 7 {AB43B89}> SB-IMPL::%EMPTY-HT-SLOT%
(LAMBDA (X) (EQUAL X '(#:G627 0))) (SB-C::ORIGINAL-SOURCE-START 0
0) (X)
(SB-C::ORIGINAL-SOURCE-START 1 1 0) (EQUAL X '(#:G627 0))
(SB-C::ORIGINAL-SOURCE-START 2 2 0) (X '(#:G627 0))
(SB-C::ORIGINAL-SOURCE-START 3 1 2 0) '(#:G627 0)
(SB-C::ORIGINAL-SOURCE-START 3 2 2 0) (#:G627 0)
(SB-C::ORIGINAL-SOURCE-START 4 1 2 2 0) (0)
(SB-C::ORIGINAL-SOURCE-START 5 1 1 2 2 0) SB-IMPL::%EMPTY-HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT%)
#((#:G627 0) #<SB-KERNEL:CONS-TYPE CONS>)
#<code object (LAMBDA (X)) {ABD6A47}>
(1 . 2)
#((#:G627 1) #<SB-KERNEL:CONS-TYPE CONS>)
(2 . 3)
#((#:G627 2) #<SB-KERNEL:CONS-TYPE CONS>)
(3 . 4)
#((#:G627 3) #<SB-KERNEL:CONS-TYPE CONS>)
(4 . 5)
#((#:G627 4) #<SB-KERNEL:CONS-TYPE CONS>)
(5 . 6)
#((#:G627 5) #<SB-KERNEL:CONS-TYPE CONS>)
(6 . 7)
#((#:G627 6) #<SB-KERNEL:CONS-TYPE CONS>)
(7 . 8)
#((#:G627 7) #<SB-KERNEL:CONS-TYPE CONS>)
(8 . 9)
#<code object (LAMBDA (X)) {ABC503F}>
#((#:G627 8) #<SB-KERNEL:CONS-TYPE CONS>)
((#:G627 8))
#<SB-KERNEL:CONSTANT :VALUE (#:G627 8) {ABF05B1}>
(9 . 10)
((#:G627 9))
#(#<HASH-TABLE :TEST EQ :COUNT 7 {ABC70A9}> SB-IMPL::%EMPTY-HT-SLOT%
(LAMBDA (X) (EQUAL X '(#:G627 9))) (SB-C::ORIGINAL-SOURCE-START 0
0) (X)
(SB-C::ORIGINAL-SOURCE-START 1 1 0) (EQUAL X '(#:G627 9))
(SB-C::ORIGINAL-SOURCE-START 2 2 0) (X '(#:G627 9))
(SB-C::ORIGINAL-SOURCE-START 3 1 2 0) '(#:G627 9)
(SB-C::ORIGINAL-SOURCE-START 3 2 2 0) (#:G627 9)
(SB-C::ORIGINAL-SOURCE-START 4 1 2 2 0) (9)
(SB-C::ORIGINAL-SOURCE-START 5 1 1 2 2 0) SB-IMPL::%EMPTY-HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-
HT-SLOT%
SB-IMPL::%EMPTY-HT-SLOT% SB-IMPL::%EMPTY-HT-SLOT%)
#((#:G627 9) #<SB-KERNEL:CONS-TYPE CONS>)
#<code object (LAMBDA (X)) {ABD6BDF}>
#<SB-KERNEL:CONSTANT :VALUE (#:G627 9) {ABDB2E1}>
10
*
if i change the lambda form, the results differ again:
* (let ((sym (gensym)))
(dotimes (x 10) (compile nil `(lambda (x) (equal x #((,sym ,x))))))
(dotimes (i 10) (sb-ext:gc))
(let ((found 0))
(dotimes (x 10)
(let ((list `(,sym ,x)))
(sb-vm::map-allocated-objects #'(lambda (object code size)
(declare (ignore code size))
(when (and (equal object
list) (not (eq object list)))
(print (cons x (incf
found)))
(sb-vm::map-referencing-
objects #'print :dynamic object)))
:dynamic)))
found))
(0 . 1)
((#:G628 0))
#((#:G628 0))
(8 . 2)
#((#:G628 8))
(9 . 3)
((#:G628 9))
#((#:G628 9))
3
*
that is, various of the ten forms have been retained.
a read through src/compiler/ didn't reveal an obvious reason for this
? am i oblivious to some feature inherent to compiled functions?
? is there some way to control this behaviour?
? is map-allocated-object imperfect, or were the missing values
really gc'd?
* (lisp-implementation-version)
"1.0.36"
* (lisp-implementation-type)
"SBCL"
* *features*
(:ANSI-CL :COMMON-LISP :SBCL :SB-DOC :SB-TEST :SB-LDB :SB-PACKAGE-LOCKS
:SB-UNICODE :SB-EVAL :SB-SOURCE-LOCATIONS :IEEE-FLOATING-
POINT :X86 :UNIX :ELF
:LINUX :SB-THREAD :LARGEFILE :GENCGC :STACK-GROWS-DOWNWARD-NOT-UPWARD
:C-STACK-IS-CONTROL-STACK :COMPARE-AND-SWAP-VOPS :UNWIND-TO-FRAME-
AND-CALL-VOP
:RAW-INSTANCE-INIT-VOPS :STACK-ALLOCATABLE-CLOSURES :STACK-
ALLOCATABLE-VECTORS
:STACK-ALLOCATABLE-LISTS :STACK-ALLOCATABLE-FIXED-OBJECTS :ALIEN-
CALLBACKS
:CYCLE-COUNTER :INLINE-CONSTANTS :LINKAGE-TABLE :OS-PROVIDES-DLOPEN
:OS-PROVIDES-PUTWC :OS-PROVIDES-SUSECONDS-T)
|