Monitoring Machine Performance

SICP > Computing with Register Machines > A Register-Machine Simulator > Monitoring Machine Performance
Previous: Generating Execution Procedures for Instructions Next: Storage Allocation and Garbage Collection

    Simulation is useful not only for verifying the correctness of a proposed machine design but also for measuring the machine's performance. For example, we can install in our simulation program a ``meter'' that measures the number of stack operations used in a computation. To do this, we modify our simulated stack to keep track of the number of times registers are saved on the stack and the maximum depth reached by the stack, and add a message to the stack's interface that prints the statistics, as shown below. We also add an operation to the basic machine model to print the stack statistics, by initializing the-ops in make-new-machine to

    (list (list 'initialize-stack
                (lambda () (stack 'initialize)))
          (list 'print-stack-statistics
                (lambda () (stack 'print-statistics))))
    
    Here is the new version of make-stack:
    (define (make-stack)
      (let ((s '())
            (number-pushes 0)
            (max-depth 0)
            (current-depth 0))
        (define (push x)
          (set! s (cons x s))
          (set! number-pushes (+ 1 number-pushes))
          (set! current-depth (+ 1 current-depth))
          (set! max-depth (max current-depth max-depth)))
        (define (pop)
          (if (null? s)
              (error "Empty stack - POP")
              (let ((top (car s)))
                (set! s (cdr s))
                (set! current-depth (- current-depth 1))
                top)))    
        (define (initialize)
          (set! s '())
          (set! number-pushes 0)
          (set! max-depth 0)
          (set! current-depth 0)
          'done)
        (define (print-statistics)
          (newline)
          (display (list 'total-pushes  '= number-pushes
                         'maximum-depth '= max-depth)))
        (define (dispatch message)
          (cond ((eq? message 'push) push)
                ((eq? message 'pop) (pop))
                ((eq? message 'initialize) (initialize))
                ((eq? message 'print-statistics)
                 (print-statistics))
                (else
                 (error "Unknown request - STACK" message))))
        dispatch))
    

    Exercises [*] through [*] describe other useful monitoring and debugging features that can be added to the register-machine simulator.

    Exercise. Measure the number of pushes and the maximum stack depth required to compute n! for various small values of n using the factorial machine shown in figure [*]. From your data determine formulas in terms of n for the total number of push operations and the maximum stack depth used in computing n! for any n > 1. Note that each of these is a linear function of n and is thus determined by two constants. In order to get the statistics printed, you will have to augment the factorial machine with instructions to initialize the stack and print the statistics. You may want to also modify the machine so that it repeatedly reads a value for n, computes the factorial, and prints the result (as we did for the GCD machine in figure [*]), so that you will not have to repeatedly invoke get-register-contents, set-register-contents!, and start.  

    Exercise. Add instruction counting to the register machine simulation. That is, have the machine model keep track of the number of instructions executed. Extend the machine model's interface to accept a new message that prints the value of the instruction count and resets the count to zero.  

    Exercise. Augment the simulator to provide for instruction tracing. That is, before each instruction is executed, the simulator should print the text of the instruction. Make the machine model accept trace-on and trace-off messages to turn tracing on and off.  

    Exercise. Extend the instruction tracing of exercise [*] so that before printing an instruction, the simulator prints any labels that immediately precede that instruction in the controller sequence. Be careful to do this in a way that does not interfere with instruction counting (exercise [*]). You will have to make the simulator retain the necessary label information.

    Exercise. Modify the make-register procedure of section [*] so that registers can be traced. Registers should accept messages that turn tracing on and off. When a register is traced, assigning a value to the register should print the name of the register, the old contents of the register, and the new contents being assigned. Extend the interface to the machine model to permit you to turn tracing on and off for designated machine registers.

    Exercise. Alyssa P. Hacker wants a breakpoint feature in the simulator to help her debug her machine designs. You have been hired to install this feature for her. She wants to be able to specify a place in the controller sequence where the simulator will stop and allow her to examine the state of the machine. You are to implement a procedure

    (set-breakpoint machine label n)
    
    that sets a breakpoint just before the nth instruction after the given label. For example,

    (set-breakpoint gcd-machine 'test-b 4)
    
    installs a breakpoint in gcd-machine just before the assignment to register a. When the simulator reaches the breakpoint it should print the label and the offset of the breakpoint and stop executing instructions. Alyssa can then use get-register-contents and set-register-contents! to manipulate the state of the simulated machine. She should then be able to continue execution by saying

    (proceed-machine machine)
    
    She should also be able to remove a specific breakpoint by means of

    (cancel-breakpoint machine label n)
    
    or to remove all breakpoints by means of

    (cancel-all-breakpoints machine)
    

    Previous: Generating Execution Procedures for Instructions Next: Storage Allocation and Garbage Collection

      webmaster@arsdigita.org