My Programming Paradigms教科书Essential of Programming Languages (3rd ed),第1章有一个练习:
练习1.12
在subst中消除对subst-in-s-exp的一次调用 用它的定义代替它并简化结果 程序。结果将是不需要的subst版本 SUBST式-S-EXP。这种技术称为内联,并由其使用 优化编译器。
原始代码有两个函数:subst
和subst-in-sexp
,它们基本上用输入列表中的新符号替换所有出现的旧符号。
(define subst
(lambda (new old slist)
(if (null? slist) '()
(cons
(subst-in-s-exp new old (car slist))
(subst new old (cdr slist))))))
(define subst-in-s-exp
(lambda (new old sexp)
(if (symbol? sexp)
(if (eqv? sexp old) new sexp)
(subst new old sexp))))
这个问题的答案是消除subst-in-sexp
,这就是
(define subst
(lambda (slist old new)
(cond
[ (null? slist) '()]
[ (eqv? (car slist) old) (cons new (subst (cdr slist) old new))]
[ else (cons (car slist) (subst (cdr slist) old new))])))
为什么衬里更好,除了它可能更短(更少的空间)?递归的大小是否会改变?换句话说,这种内联是否会创建更少的堆栈元素?
此外,我如何使用这个想法来更快地制作我的C ++,Python和Java代码?我可以轻松地扩展这个想法吗?感谢。
我在Scheme(实际上是Racket)中对此进行了标记,因为这是本书中语言的选择。
答案 0 :(得分:4)
内联是一个非常标准的编译器优化,但正如AoeAoe所说,编写代码通常更好,以便它可读并让编译器为你完成所有内联。
内联的直接好处是它消除了代码中的分支。这意味着您的CPU可以直接读取代码,而不必花费几个时钟周期来查找要执行的下一部分代码。
然而,内联也有一些其他好处。最终会得到更大的代码块,这意味着编译器可以使用更多的代码和数据。它可能能够在寄存器中粘贴更多东西,或者进行常量折叠以消除更多计算。编译器也可以更好地执行指令调度,因为它有更多的指令可以移动。
缺点是内联会增加您生成的代码大小。特别是现代CPU运行速度比内存快得多,为了将所有热门代码保存在L1缓存中,通常可以更好地内联更少的代码。
答案 1 :(得分:3)
在Eric的回答中添加一点内容,内联在动态类型语言中可能是一个巨大的胜利,其中内联调用可以使编译器将实现专门化为出现的各种数据。
例如:假设我有一个名为f:
的函数(define (f x) (+ (* x x) 3.0))
......我称之为内联:
(+ (f 3.2) (g 3.9))
在这种情况下,内联代码清楚地表明不能使用非数字调用乘法和加法,因此可以省略此错误检查。
答案 2 :(得分:1)
回答:“我如何利用这个想法让我的C ++,Python和Java代码更快?”
我认为Python运行时不会像自动内联小方法那样。 (如果这是错的,有人请指正!)所以在Python中,如果你有一段非常性能敏感的代码,可能在内部循环中运行数万或数百万次,你可以尝试手动内联。如果代码确实是一个瓶颈,只有这样做,并且它确实需要尽可能快,并且总是测量以确定这样的优化是否实际上有帮助。 (如果您尝试内联并且没有帮助,最好撤消优化,因为内联通常会使您的代码更难阅读。)
在Java和C ++中,任何好的编译器都会为您编写小方法。您可以(有时)做的事情是帮助编译器看到可以内联的方法。如果调用的确切方法取决于对象的运行时类型(如在C ++中使用virtual
方法时),则编译器将无法内联调用。 Java中的static
方法可以很容易地内联,并且将方法声明为final
(当这样做时有意义)也可以使编译器内联。
如果您将来了解有关编译器及其工作原理的更多信息,您将能够更好地了解如何以编译器能够为您优化的方式编写对性能敏感的代码。