The SBCL statistical profiler does not show an entry for every feature called

I am using the SBCL statistical profiler to profile these features:

(defun fact-rec (n)
  (if (zerop n)
      1
      (* n (fact-rec (1- n)))))

(defun fact-call (n)
  (fact-rec n))

(defun fact-iter (n)
  (loop :with accu = 1
    :for i :upfrom 2 :to n
    :doing (setf accu (* i accu))
    :finally (return accu)))

(defun fact-opti-iter (n)
  (let ((accu 1))
    (tagbody
     loop
       (unless (zerop n)
         (setf accu (* n accu))
         (decf n)
         (go loop)))
    accu))

      

To estimate the weight of the recursive version, I defined a function fact-call

that stays on the stack below all calls fact-rec

so that it can be tracked correctly. Here is my profiling code:

(sb-sprof:profile-call-counts 'fact-rec 'fact-call 'fact-iter 'fact-opti-iter)
(sb-sprof:with-profiling (:max-samples 1000
                   :loop nil
                   :report :flat)
       (dotimes (i 1500)
         (fact-call i)
         (fact-iter i)
         (fact-opti-iter i)))

      

Thus, it is fact-rec

never called directly, so if it appears in the profiler report, it is necessarily called fact-call

. But here is the report I am getting:

Profiler sample vector full (70 traces / 10000 samples), doubling the size
Profiler sample vector full (133 traces / 20,000 samples), doubling the size

Number of samples: 195
Sample interval: 0.01 seconds
Total sampling time: 1.9499999 seconds
Number of cycles: 0
Sampled threads:
 #

           Self Total Cumul
  Nr Count% Count% Count% Calls Function
-------------------------------------------------- ----------------------
   1 193 99.0 193 99.0 193 99.0 - SB-BIGNUM: MULTIPLY-BIGNUM-AND-FIXNUM
   2 1 0.5 195 100.0 194 99.5 - SB-KERNEL: TWO-ARG- *
   3 1 0.5 1 0.5 195 100.0 - SB-BIGNUM ::% NORMALIZE-BIGNUM
   4 0 0.0 195 100.0 195 100.0 - "Unknown component: # x100317AB30"
   5 0 0.0 195 100.0 195 100.0 - SB-INT: SIMPLE-EVAL-IN-LEXENV
   6 0 0.0 195 100.0 195 100.0 - EVAL
   7 0 0.0 195 100.0 195 100.0 - SWANK :: EVAL-REGION
   8 0 0.0 195 100.0 195 100.0 - (LAMBDA NIL: IN SWANK-REPL :: REPL-EVAL)
   9 0 0.0 195 100.0 195 100.0 - SWANK-REPL :: TRACK-PACKAGE
  10 0 0.0 195 100.0 195 100.0 - SWANK :: CALL-WITH-RETRY-RESTART
  11 0 0.0 195 100.0 195 100.0 - SWANK :: CALL-WITH-BUFFER-SYNTAX
  12 0 0.0 195 100.0 195 100.0 - SWANK-REPL :: REPL-EVAL
  13 0 0.0 195 100.0 195 100.0 - SWANK: EVAL-FOR-EMACS
  14 0 0.0 195 100.0 195 100.0 - SWANK :: PROCESS-REQUESTS
  15 0 0.0 195 100.0 195 100.0 - (LAMBDA NIL: IN SWANK :: HANDLE-REQUESTS)
  16 0 0.0 195 100.0 195 100.0 - SWANK / SBCL :: CALL-WITH-BREAK-HOOK
  17 0 0.0 195 100.0 195 100.0 - (FLET SWANK / BACKEND: CALL-WITH-DEBUGGER-HOOK: IN "/Users/vleo/quicklisp/dists/quicklisp/software/slime-v2.19/swank/sbcl.lisp")
  18 0 0.0 195 100.0 195 100.0 - SWANK :: CALL-WITH-BINDINGS
  19 0 0.0 195 100.0 195 100.0 - SWANK :: HANDLE-REQUESTS
  20 0 0.0 195 100.0 195 100.0 - (LABELS SWANK / SBCL :: RUN: IN SWANK / BACKEND: ADD-FD-HANDLER)
  21 0 0.0 195 100.0 195 100.0 - SB-IMPL :: SUB-SUB-SERVE-EVENT
  22 0 0.0 195 100.0 195 100.0 - SB-IMPL :: SUB-SERVE-EVENT
  23 0 0.0 195 100.0 195 100.0 - SB-SYS: WAIT-UNTIL-FD-USABLE
  24 0 0.0 195 100.0 195 100.0 - SB-IMPL :: REFILL-INPUT-BUFFER
  25 0 0.0 195 100.0 195 100.0 - SB-IMPL :: INPUT-CHAR / UTF-8
  26 0 0.0 195 100.0 195 100.0 - (LAMBDA (& REST REST): IN SB-IMPL :: GET-EXTERNAL-FORMAT)
  27 0 0.0 195 100.0 195 100.0 - READ-CHAR
  28 0 0.0 195 100.0 195 100.0 - SB-IMPL ::% READ-PRESERVING-WHITESPACE
  29 0 0.0 195 100.0 195 100.0 - READ
  30 0 0.0 195 100.0 195 100.0 - SB-IMPL :: REPL-READ-FORM-FUN
  31 0 0.0 195 100.0 195 100.0 - SB-IMPL :: REPL-FUN
  32 0 0.0 195 100.0 195 100.0 - (LAMBDA NIL: IN SB-IMPL :: TOPLEVEL-REPL)
  33 0 0.0 195 100.0 195 100.0 - SB-IMPL ::% WITH-REBOUND-IO-SYNTAX
  34 0 0.0 195 100.0 195 100.0 - SB-IMPL :: TOPLEVEL-REPL
  35 0 0.0 195 100.0 195 100.0 - SB-IMPL :: TOPLEVEL-INIT
  36 0 0.0 195 100.0 195 100.0 - (FLET #: WITHOUT-INTERRUPTS-BODY-74: IN SAVE-LISP-AND-DIE)
  37 0 0.0 195 100.0 195 100.0 - (LABELS SB-IMPL :: RESTART-LISP: IN SAVE-LISP-AND-DIE)
  38 0 0.0 69 35.4 195 100.0 1500 FACT-OPTI-ITER
  39 0 0.0 65 33.3 195 100.0 1125750 FACT-REC
  40 0 0.0 61 31.3 195 100.0 1500 FACT-ITER
-------------------------------------------------- ----------------------
          0 0.0 elsewhere

There is no mention of it fact-call

, although he certainly was summoned. In addition, there is an entry for fact-rec

. If the call is fact-call

deeper in the stack and is recorded fact-rec

, shouldn't it be recorded?

thank

+3


source to share


1 answer


Are you sure the call fact-call

remains on the stack? Have you checked it out?

The SBCL compiler can do TCO (tail call optimization). The function fact-call

calls fact-rec

at the tail position. This function call can be replaced with a jump, reusing the current stack stack.

TCO

Change fact-rec

to call break

and we can look at the stack:

(defun fact-rec (n)
  (if (zerop n)
      (progn (break) 1)
    (* n (fact-rec (1- n)))))

(defun fact-call (n)
  (fact-rec n))       ; tail call to FACT-REC

      

Let's call it test

:

(defun test ()
 (fact-call 4))

      

The reverse move looks like this: no fact-call

.



Backtrace:
  0: (FACT-REC 0)
  1: (FACT-REC 1)
  2: (FACT-REC 2)
  3: (FACT-REC 3)
  4: (FACT-REC 4)

      

No TCO

Now we tell the SBCL compiler not to use TCO internally fact-call

:

(defun fact-call (n)
  (declare (optimize (debug 3)))    ; debug level 3 does no TCO
  (fact-rec n))

      

Now this is the call stack:

Backtrace:
  0: (FACT-REC 0)
  1: (FACT-REC 1)
  2: (FACT-REC 2)
  3: (FACT-REC 3)
  4: (FACT-REC 4)
  5: (FACT-CALL 4)

      

As you can see, the call fact-call

remains on the stack.

+5


source







All Articles