考虑一下Chez Scheme代码:
(import (chezscheme)) (define (list-enumerate ls val proc) (let loop ((ls ls) (return? #f) (val val)) (if (or (null? ls) return?) val (call-with-values (lambda () (proc val (car ls))) (lambda (return? val) (loop (cdr ls) return? val)))))) (define (list-index ls proc) (list-enumerate ls 0 (lambda (i elt) (if (proc elt) (values #t i) (values #f (+ i 1)))))) (define n 100000) (define data (iota n)) (time (list-index data (lambda (elt) (= elt (- n 1)))))
运行它:
~ $ scheme --script ~/scratch/_list-enumerate-allocation-test-chez-a.sps (time (list-index data ...)) no collections 3 ms elapsed cpu time 4 ms elapsed real time 8 bytes allocated
哇,报告说只分配了8个字节。
让我们使用--program
选项而不是--script
再次运行它:
~ $ scheme --program ~/scratch/_list-enumerate-allocation-test-chez-a.sps (time (list-index data ...)) no collections 3 ms elapsed cpu time 3 ms elapsed real time 800000 bytes allocated
Yikes,分配了800000个字节。
有什么区别?
版
答案 0 :(得分:4)
以下是Kent Dybvig的回复:
这是一个有趣的问题。
使用--script运行时,它使用REPL语义,变量 在脚本中定义,如list-enumerate和list-index,是可变的, 这抑制了过程间优化,包括内联。什么时候 然而,运行--program,变量是不可变的,允许 过程间优化。
在这种情况下, - program允许编译器内联list-enumerate list-index的主体,反过来又是list-index中的lambda表达式 body进入list-enumerate的主体。最终结果是有条件的 call-with-values生成器表达式中的表达式。这导致 编译器为消费者创建一个闭包,以避免代码 沿条件的then和else分支重复。这个 每次通过list-enumerate的循环创建闭包,从而产生 额外的分配开销。这就是优化经常发生的方式。 大多数情况下你赢了,但有时你输了。总的来说,好消息是 即使在您的计划中,也可以减轻他的成本。我打电话给 循环中的list-index(修改后的代码在下面)并发现那个 - 程序,代码运行速度提高了约30%。
肯特
(import (chezscheme)) (define (list-enumerate ls val proc) (let loop ((ls ls) (return? #f) (val val)) (if (or (null? ls) return?) val (call-with-values (lambda () (proc val (car ls))) (lambda (return? val) (loop (cdr ls) return? val)))))) (define (list-index ls proc) (list-enumerate ls 0 (lambda (i elt) (if (proc elt) (values #t i) (values #f (+ i 1)))))) (define n 100000) (define data (time (iota n))) (let () (define runalot (lambda (i thunk) (let loop ([i i]) (let ([x (thunk)]) (if (fx= i 1) x (loop (fx- i 1))))))) (time (runalot 1000 (lambda () (list-index data (lambda (elt) (= elt (- n 1))))))))