;;;; Room, Space (in-package "SYSTEM") ;;----------------------------------------------------------------------------- ;; ROOM (definternational room-format (t ENGLISH)) (deflocalized room-format ENGLISH (list (formatter "Class~VT# Instances Size (bytes) Average size~%") (formatter "-----~VT----------- ------------ ------------~%") (formatter "~VT~8D ~9D ~13,3F~%") ) ) (defun room (&optional (kind :default)) (unless (or (eq kind 'nil) (eq kind ':default) (eq kind 't)) (error (TEXT "~S: argument must be ~S, ~S or ~S, not ~S") 'room 'nil 't ':default kind)) ;; Get the figures now, because (heap-statistics) causes heap allocation. (multiple-value-bind (used room static gc-count gc-space gc-time) (sys::%room) (when (eq kind 't) (let ((stat (heap-statistics))) ;; stat = #( ... (classname num-instances . num-bytes) ...) (setq stat (sort stat #'> :key #'cddr)) (let* ((localinfo (localized 'room-format)) (header-line (first localinfo)) (separator-line (second localinfo)) (data-line (third localinfo)) (midcol (- (or *print-right-margin* sys::*prin-linelength*) 39)) (total-instances 0) (total-bytes 0)) (fresh-line) (format t header-line midcol) (format t separator-line midcol) (dotimes (i (length stat)) (let* ((stat-record (svref stat i)) (classname (car stat-record)) (instances (cadr stat-record)) (bytes (cddr stat-record))) (when (plusp instances) (prin1 classname) (format t data-line midcol instances bytes (/ (float bytes 0d0) instances)) (incf total-instances instances) (incf total-bytes bytes)))) (format t separator-line midcol) (write-string (TEXT "Total")) (format t data-line midcol total-instances total-bytes (/ (float total-bytes 0d0) total-instances))))) (terpri) (unless (eq kind 'nil) (format t (TEXT "Number of garbage collections: ~16:D~%~ Bytes freed by GC: ~16:D~%~ Time spent in GC: ~16F sec") gc-count gc-space (float (/ gc-time internal-time-units-per-second))) (terpri)) (format t (TEXT "Bytes permanently allocated: ~16:D~%~ Bytes currently in use: ~16:D~%~ Bytes available until next GC: ~16:D") static used room) (terpri) (values used room static gc-count gc-space gc-time))) ;;----------------------------------------------------------------------------- ;; SPACE ;; Recall the macro SPACE, making (space form) roughly equivalent to ; ; (multiple-value-bind (var1 var2 var3 var4) (%space1) ; (let ((*gc-statistics* (1+ (max *gc-statistics* 0)))) ; (setq var3 (multiple-value-list form)) ; (setq var4 (%space2)) ; ) ; (%space var1 var2 var3 var4) ; (values-list var3) ; ) ;; It works like this: ;; 1. Do a GC, to eliminate unused objects, and call (heap-statistics) ;; to get a statistics of all live objects. ;; 2. Enable the GC reclaimage statistics: Call (gc-statistics), ;; then bind *gc-statistics* to at least 1. ;; 3. Evaluate the form. ;; 4. Convert the form's values to a list. ;; 5. Call (gc), so that the GC eliminates temporarily allocated objects. ;; 6. Call (gc-statistics) again. We can now unbind *gc-statistics*. ;; 7. Call (heap-statistics) again, to get a statistics of all now live ;; objects. ;; ==> The difference of the two (gc-statistics) calls will comprise exactly ;; the temporary allocations of the form. ;; ==> The difference of the two (heap-statistics) calls will comprise ;; - the resulting data structure of the first call to (heap-statistics), ;; - the permanent allocations of the form, ;; - the list allocated to hold the form's values, ;; - the data structures which form the ldifference between the two ;; calls to (gc-statistics). (defun %space1 () ; ABI (gc) (values (heap-statistics) (gc-statistics) ) ) ; %space1 returns two values, so var3 and var4 are initially bound to nil. (defun %space2 () ; ABI (gc) (gc-statistics) ) (defun %space (heap-stat-1 gc-stat-1 vallist gc-stat-2) ; ABI (let ((heap-stat-2 (heap-statistics))) ;; Now we have all the statistics, and are free to do any kind ;; of allocations. (let ((ht (make-hash-table :key-type 't :value-type '(cons cons cons) :test 'fasthash-eq))) ;; For each type, (gethash type ht) contains a cons ;; (heap-stat-record . gc-stat-record), ;; where both records are conses (n-instances . n-bytes). (flet ((add (statv accessor incrementer) ;; Adds the contents of statv to the hash table. ;; accessor = #'car or #'cdr, incrementer = #'+ or #'-. (dotimes (i (length statv)) (let ((stat-record (svref statv i))) (let ((name (car stat-record)) (n-instances (cadr stat-record)) (n-bytes (cddr stat-record))) (unless (and (zerop n-instances) (zerop n-bytes)) (let ((htx (gethash name ht))) (unless htx (setf (gethash name ht) (setf htx (cons (cons 0 0) (cons 0 0))) ) ) (let ((record (funcall accessor htx))) (setf (car record) (funcall incrementer (car record) n-instances) ) (setf (cdr record) (funcall incrementer (cdr record) n-bytes) ) )) ) ) ) ) ) ) ;; The difference between the two (gc-statistics) calls. (do ((l gc-stat-2 (cdr l))) ((eq l gc-stat-1)) (add (car l) #'cdr #'+) ) ;; The difference between the two (heap-statistics) calls. (add heap-stat-2 #'car #'+) (add heap-stat-1 #'car #'-) ;; Adjust for the statistics data structures themselves. (add (heap-statistics-statistics heap-stat-1) #'car #'-) (add (list-statistics vallist) #'car #'-) (add (gc-statistics-statistics gc-stat-1 gc-stat-2) #'car #'-) ) ;; Now transform the hash table into a list, and sort it. (let ((statlist '())) (maphash #'(lambda (name htx) (push (cons name htx) statlist)) ht) ;; statlist is now a list of (name heap-stat-record . gc-stat-record). (setq statlist (sort statlist #'> :key #'(lambda (r) (+ (cdr (cadr r)) (cdr (cddr r)))) ) ) ;; Display the statistics. (space-tabulate statlist) ;; Done. The return value is ignored. ) ) ) ) (definternational space-format (t ENGLISH)) (deflocalized space-format ENGLISH (list (formatter "~VT Permanent Temporary~%") (formatter "Class~VTinstances bytes instances bytes~%") (formatter "-----~VT--------- --------- --------- ---------~%") (formatter "~VT~9D ~9D ~9D ~9D~%") ) ) (defun space-tabulate (statlist) (let* ((localinfo (localized 'space-format)) (header-line1 (first localinfo)) (header-line2 (second localinfo)) (separator-line (third localinfo)) (data-line (fourth localinfo)) (midcol (- (or *print-right-margin* sys::*prin-linelength*) 40)) (total-perm-instances 0) (total-perm-bytes 0) (total-temp-instances 0) (total-temp-bytes 0) (stream *trace-output*)) (fresh-line stream) (format stream header-line1 midcol) (format stream header-line2 midcol) (format stream separator-line midcol) (dolist (stat-record statlist) (let* ((classname (car stat-record)) (permstat (cadr stat-record)) (tempstat (cddr stat-record)) (perm-instances (car permstat)) (perm-bytes (cdr permstat)) (temp-instances (car tempstat)) (temp-bytes (cdr tempstat))) (unless (and (zerop perm-instances) (zerop perm-bytes) (zerop temp-instances) (zerop temp-bytes) ) (prin1 classname stream) (format stream data-line midcol perm-instances perm-bytes temp-instances temp-bytes ) (incf total-perm-instances perm-instances) (incf total-perm-bytes perm-bytes) (incf total-temp-instances temp-instances) (incf total-temp-bytes temp-bytes) ) ) ) (format stream separator-line midcol) (write-string (TEXT "Total") stream ) (format stream data-line midcol total-perm-instances total-perm-bytes total-temp-instances total-temp-bytes ) ) )