2014-02-24 2 views
0

Я хотел бы написать простой профайлер для схемы, который дает подсчет количества раз, когда вызывается каждая функция в программе. Я попытался переопределить команду 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), но тогда я не могу рассчитывать или сортировать или иным образом обрабатывать имена функций.

Как написать простой профайлер для схемы?

EDIT: Вот мой простой профайлер (функция 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)) 

А вот это улучшенная версия функции, которая останавливает пробное деление на корень квадратный из п:

(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 за их ответы.

+0

также нельзя установить 'set!' Имя, которое еще не было 'define'd (хотя есть, например, ['(compile-allow-set! -Undefined allow?)'] (Http: // docs.racket-lang.org/reference/eval.html?q=set!#%28def._%28%28quote._~23~25kernel%29._compile-allow-set!-undefined%29%29) в Ракетка). –

+0

@WillNess: В схеме RnRS вы правы. Различные реализации могут или не могут позволить вам «установить!» Неопределенное имя. Например, Chez Scheme, который я обычно использую, позволяет это. – user448810

ответ

2

Изменить свою линию, которая говорит:

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

в

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

Оценка name в теле определение функции name является процедура для name. При цитировании вы избегаете оценки и оставляете символ/идентификатор. Как вы и надеялись, ваша *profile* переменная будет растущим списком с одним символом для каждого вызова функции. Вы можете подсчитать количество вхождений данного имени.

+1

Спасибо! Я должен был это знать. – user448810

2

Вот пример способа его реализации. Это написано в Racket, но тривиально, чтобы перейти на ваш диалект Схемы.

без синтаксиса

Давайте попробуем без макросов первого.

Вот процедура профиля:

(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)) 

EDIT применен к последнему примеру:

(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))