方案的块结构效率

时间:2019-11-12 22:06:35

标签: scheme lisp racket sicp

The book在第1章中定义了块结构,使您可以将define封装在过程定义中。

例如考虑以下mean-square定义:

(define (mean-square x y) 
    (define (square x) (* x x))
    (define (average x y) (/ (+ x y) 2))
    (average (square x) (square y)))

当我运行(mean-square 2 4)时,我会正确获得10

我的问题是,内部定义(此玩具盒中的squareaverage是否每次运行 我通过解释器调用mean-square过程?如果是这样,那不是效率低下吗?如果没有,为什么?

4 个答案:

答案 0 :(得分:2)

如果代码天真地被编译,则可能会有一些开销。原因是内部函数是在全新的词法环境中定义的,该词法环境在每次进入函数时都会实例化。在抽象语义中,每次调用函数时,都必须捕获新的词法闭包并将其连接到该环境框架中的正确位置。

因此,归结为编译器可以优化多少。例如,它可以注意到,这两个函数均未实际引用周围的词汇环境。 (这些函数中的xy引用是它们自己的参数,而不是周围的mean-square的参数)。这意味着它们都无需更改语义即可移至最高层:

(define (__anon1 x) (* x x))

(define (__anon2 x y) (/ (+ x y) 2))

(define (mean-square x y)
    (define square __anon1)
    (define average __anon2)
    (average (square x) (square y)))

并且从现在开始squareaverage是有效的简单别名(对于由编译器生成的全局实体的别名,编译器知道该别名不会被其控制范围之外的任何事物操纵),它们表示的值可以通过以下方式传播:

(define (mean-square x y)
  (__anon2 (__anon1 x) (__anon1 y)))

答案 1 :(得分:1)

这不是问题。编译mean-square过程时,还将嵌套所有嵌套过程。不必每次调用mean-square过程时都重新编译它们。

答案 2 :(得分:1)

我认为其他答案可能已经使您相信,您给出的案例确实不需要任何开销:可以将本地定义编译掉。但是,值得思考的是系统如何处理无法完成 的情况。

考虑这样的定义:

(define (make-searcher thing)
  (define (search in)
    (cond [(null? in)
           #f]
          [(eqv? (first in) thing)
           in]
          [else (search (rest in))]))
  search)

嗯,本地search过程绝对不能在这里编译,因为它是从make-searcher返回的。而且甚至比这更糟:(make-searcher 1)(make-searcher 2)需要返回不同的过程,因为((make-searcher 1) '(1 2 3))(1 2 3)((make-searcher 2) '(1 2 3))(2 3)

所以这听起来完全没有希望:本地search过程不仅必须是一个过程(不能被编译掉),而且每次都必须重新制作。

但是实际上情况并没有那么糟。词法作用域意味着系统可以准确知道search可以看到哪些绑定(在这种情况下,thing的绑定及其参数)。因此,例如,您可以做的是编译一些代码,这些代码在向量中查找这些绑定的值。然后,从make-search返回的内容将search的已编译代码与绑定向量打包在一起。编译后的代码始终相同,每次只需要创建和初始化向量。

答案 3 :(得分:0)

想象一下这段代码:

(let ((a expr))
  (do-something-with a))

与以下相同:

((lambda (a)
   (do-something-with a))
 expr)

在解释器中,它可能每次在调用lambda之前创建一次lambda 语言可能会将其变成(do-something-with expr)。该报告除了保证尾部递归外,不希望涉及非功能性要求。在所有严肃的实现中,lambda都是便宜的。

由于您提到球拍: 文件test_com.rkt

#lang racket
(define (mean-square x y) 
    (define (square x) (* x x))
    (define (average x y) (/ (+ x y) 2))
    (average (square x) (square y)))

(display (mean-square 2 4))

端子命令:

raco make test_com.rkt
raco decompile compiled/test_com_rkt.zo

结果输出:

(module test_com ....
  (require (lib "racket/main.rkt"))
  (provide)
  (define-values
   (mean-square)
   (#%closed
    mean-square49
    (lambda (arg0-50 arg1-51)
      '#(mean-square #<path:/home/westerp/compiled/test_com.rkt> 2 0 14 136 #f)
      '(flags: preserves-marks single-result)
      (/ (+ (* arg0-50 arg0-50) (* arg1-51 arg1-51)) '2))))
  (#%apply-values print-values (display '10)) ; the only code that matters!
  (void)
  (module (test_com configure-runtime) ....
    (require '#%kernel (lib "racket/runtime-config.rkt"))
    (provide)
    (print-as-expression '#t)
    (void)))

虽然mean-square内联了本地过程,但是因为我给它提供了文字值,所以它将永远不会调用它,所以它所做的只是(display '10)然后退出。

这当然是如果您执行makeexe。在DrRacket中,启用调试以及更好的跟踪和错误消息的语言选项将运行得更慢。