如何为Scheme编写一个简单的探查器

时间:2014-02-24 04:02:47

标签: scheme profiler

我想为Scheme编写一个简单的分析器,它可以计算程序中每个函数的调用次数。我试图像这样重新定义define命令(最终我将添加其他形式的define,但是现在我只是想编写概念验证代码):

(define-syntax define
  (syntax-rules ()
    ((define (name args ...) body ...)
      (set! name
        (lambda (args ...)
          (begin
            (set! *profile* (cons name *profile*))
            body ...))))))

我的想法是在列表中*profile*每次调用一个函数,然后再检查列表并确定函数计数。这可以工作,但是存储函数本身(即函数名的可打印表示,在Chez Scheme中#<procedure f>用于名为f的函数,但是我无法计算或排序或以其他方式处理函数名称。

如何为Scheme编写简单的探查器?

编辑:这是我的简单探查器(uniq-c函数计算列表中的相邻重复项来自我的Standard Prelude):

(define *profile* (list))

(define (reset-profile)
  (set! *profile* (list)))

(define-syntax define-profiling
  (syntax-rules ()
    ((_ (name args ...) body ...)
      (define (name args ...)
          (begin
            (set! *profile*
              (cons 'name *profile*))
            body ...)))))

(define (profile)
  (uniq-c string=?
    (sort string<?
      (map symbol->string *profile*)))))

作为一个简单的演示,这里是一个通过试验分区识别素数的函数。函数divides?分开,因为探查器只计算函数调用,而不是单个语句。

(define-profiling (divides? d n)
  (zero? (modulo n d)))

(define-profiling (prime? n)
  (let loop ((d 2))
    (cond ((= d n) #t)
          ((divides? d n) #f)
          (else (loop (+ d 1))))))

(define-profiling (prime-pi n)
  (let loop ((k 2) (pi 0))
    (cond ((< n k) pi)
          ((prime? k) (loop (+ k 1) (+ pi 1)))
          (else (loop (+ k 1) pi)))))

> (prime-pi 1000)
168
> (profile)
(("divides?" . 78022) ("prime-pi" . 1) ("prime?" . 999))

这是该函数的改进版本,它在 n 的平方根处停止试验分割:

(define-profiling (prime? n)
  (let loop ((d 2))
    (cond ((< (sqrt n) d) #t)
          ((divides? d n) #f)
          (else (loop (+ d 1))))))

> (reset-profile)
> (prime-pi 1000)
168
> (profile)
(("divides?" . 5288) ("prime-pi" . 1) ("prime?" . 999))

我将在my blog进行有关分析的更多内容。感谢@uselpa和@GoZoner的回答。

2 个答案:

答案 0 :(得分:2)

这是实现它的一种示例方式。它用Racket编写,但很容易转换为你的Scheme方言。

没有语法

让我们先尝试不使用宏。

这是个人资料程序:

(define profile
  (let ((cache (make-hash)))  ; the cache memorizing call info
    (lambda (cmd . pargs)     ; parameters of profile procedure
      (case cmd
        ((def) (lambda args                  ; the function returned for 'def
                 (hash-update! cache (car pargs) add1 0) ; prepend cache update
                 (apply (cadr pargs) args))) ; call original procedure
        ((dmp) (hash-ref cache (car pargs))) ; return cache info for one procedure
        ((all) cache)                        ; return all cache info
        ((res) (set! cache (make-hash)))     ; reset cache
        (else  (error "wot?"))))))           ; unknown parameter

以及如何使用它:

(define test1 (profile 'def 'test1 (lambda (x) (+ x 1))))
(for/list ((i 3)) (test1 i))
=> '(1 2 3)
(profile 'dmp 'test1)
=> 3

添加语法

(define-syntax define!
  (syntax-rules ()
    ((_ (name args ...) body ...)
     (define name (profile 'def 'name (lambda (args ...) body ...))))))

(define! (test2 x) (* x 2))
(for/list ((i 4)) (test2 i))
=> '(0 2 4 6)
(profile 'dmp 'test2)
=> 4

转储全部:

(profile 'all)
=> '#hash((test2 . 4) (test1 . 3))

编辑应用于您的上一个示例:

(define! (divides? d n) (zero? (modulo n d)))

(define! (prime? n)
  (let loop ((d 2))
    (cond ((< (sqrt n) d) #t)
          ((divides? d n) #f)
          (else (loop (+ d 1))))))

(define! (prime-pi n)
  (let loop ((k 2) (pi 0))
    (cond ((< n k) pi)
          ((prime? k) (loop (+ k 1) (+ pi 1)))
          (else (loop (+ k 1) pi)))))

(prime-pi 1000)
=> 168
(profile 'all)
=> '#hash((divides? . 5288) (prime-pi . 1) (prime? . 999))

答案 1 :(得分:2)

更改您的行:

    (set! *profile* (cons name *profile*))

    (set! *profile* (cons 'name *profile*))

name函数正文中name的评估是name的过程。通过引用来避免评估,并留下符号/标识符。正如您所希望的那样,您的*profile*变量将成为一个不断增长的列表,每个函数调用都有一个符号。您可以计算给定名称的出现次数。