改进Racket的time函数

Racket和Chez Scheme一样都有time函数,用来打印函数的执行时间,所不同的地方在于,Chez Scheme的time函数会把分配的字节数也显示出来,这对于我们调试和优化代码很有帮助,不过Matthew Flatt并没有打算将Racket的time函数添加这一功能,尽管如此,Matthew Flatt提供了一个思路,只是稍微麻烦一些。

Chez Scheme的time函数打印的格式如下:

19 collections

0.201254290s elapsed cpu time, including 0.183228711s collecting

0.201678000s elapsed real time, including 0.183665000s collecting

160012160 bytes allocated, including 113015168 bytes reclaimed

一下是改进的time函数,名字叫time2:

(define (time2 proc lst)
    (let ([mem-before (current-memory-use 'cumulative)])
        (let-values ([(r cpu-time real-time gc-time) (time-apply proc lst)])
            (let ([mem-now (current-memory-use 'cumulative)])
                (values
                    r
                    (format "~ams elapsed cpu time" cpu-time)
                    (format "~ams elapsed real time" real-time)
                    (format "~ams elapsed gc time" gc-time)
                    (format "~a bytes allocated" (- mem-now mem-before)))))))


> (time2 make-list '(10000000 0))
"177ms elapsed cpu time"
"178ms elapsed real time"
"153ms elapsed gc time"
"160100112 bytes allocated"


(time2 make-bytes '(10000))
"0ms elapsed cpu time"
"0ms elapsed real time"
"0ms elapsed gc time"
"10544 bytes allocated"

分配的字节数不会非常精确,比如没有考虑多线程的情况,不过在REPL中倒是可以使用,基本上不会有太大的偏差。

current-memory-use和custodian

current-memory-use函数的参数有3种模式:

(current-memory-use [mode]) → exact-nonnegative-integer?

mode : (or/c #f ‘cumulative custodian?) = #f

第三种mode叫做 custodian? , 这是Racket提供的一种监护机制.

custodian 可以用来管理一组线程、文件流端口、TCP端口、TCP监听器、UDP sockets、字节转换器、place。当线程或者端口等等被创建时,它就置于(current-custodian)的监护之下,除了root custodian, 每一个custodian都处于其父级的custodian的管理之下,这样就形成了一个层次结构。

当一个custodian被( custodian-shutdown-all )关闭时,它会立即且强制终结其下管理的线程、端口等等。

custodian在很多场景中很有用,比如用于释放资源:

#lang racket


(define done-chan (make-channel))

(define (print-hello)
  (define my-cust (make-custodian)) ;; 定义监护人,便于清理所有相关资源

  (parameterize ([current-custodian my-cust])
    (thread
        (lambda ()
            (let loop ()
                (displayln "Hello")
                (sleep 3)
                (loop))))
    (thread
        (lambda ()
            (let loop ()
                (define item (channel-get done-chan))
                (custodian-shutdown-all my-cust))))))


(print-hello)

;; (channel-put done-chan #t)

这段代码创建了一个custodian, 并启动了两个线程,线程由这个custodian管理((parameterize ([current-custodian my-cust])))。一个线程每隔3秒钟打印一个“Hello”,永不停止。另一个线程等待done-chan 消息,如果有消息,则关闭custodian, 同时关闭这个custodian下的两个线程,也就释放了资源。

custodian还可以用来限制内存的使用量:


(custodian-limit-memory
   (current-custodian) (* 2 1024))

(define x (make-bytes (* 4 1024)))

运行这段代码,会提示out of memory(在REPL无效)。


欢迎加入Racket 隐修会 :731859928(QQ)