13.6 快速操作符 (Fast Operators)
本章一开始就宣称 Lisp 是两种不同的语言。就某种意义来讲这确实是正确的。如果你仔细看过 Common Lisp 的设计,你会发现某些特性主要是为了速度,而另外一些主要为了便捷性。
例如,你可以通过三个不同的函数取得向量给定位置上的元素: elt
、 aref
、 svref
。如此的多样性允许你把一个程序的性能提升到极致。 所以如果你可以使用 svref
,完事儿! 相反,如果对某段程序来说速度很重要的话,或许不应该调用 elt
,它既可以用于数组也可以用于列表。
对于列表来说,你应该调用 nth
,而不是 elt
。然而只有单一的一个函数 ── length
── 用于计算任何一个序列的长度。为什么 Common Lisp 不单独为列表提供一个特定的版本呢?因为如果你的程序正在计算一个列表的长度,它在速度上已经输了。在这个 例子中,就像许多其他的例子一样,语言的设计暗示了哪些会是快速的而哪些不是。
另一对相似的函数是 eql
和 eq
。前者是验证同一性 (identity) 的默认判断式,但如果你知道参数不会是字符或者数字时,使用后者其实更快。两个对象 eq 只有当它们处在相同的内存位置上时才成立。数字和字符可能不会与任何特定的内存位置相关,因此 eq
不适用于它们 (即便多数实现中它仍然能用于定长数)。对于其他任何种类的参数, eq
和 eql
将返回相同的值。
使用 eq
来比较对象总是最快的,因为 Lisp 所需要比较的仅仅是指向对象的指针。因此 eq
哈希表 (如图 13.5 所示) 应该会提供最快的访问。 在一个 eq
哈希表中, gethash
可以只根据指针查找,甚至不需要查看它们指向的是什么。然而,访问不是唯一要考虑的因素; eq 和 eql 哈希表在拷贝型垃圾回收算法 (copying garbage collection algorithm)中会引起额外的开销,因为垃圾回收后需要对一些哈希值重新进行计算 (rehashing)。如果这变成了一个问题,最好的解决方案是使用一个把定长数作为键值的 eql
哈希表。
当被调函数有一个余留参数时,调用 reduce
可能是比 apply
更高效的一种方式。例如,相比
(apply #'+ '(1 2 3))
写成如下可以更高效:
(reduce #'+ '(1 2 3))
它不仅有助于调用正确的函数,还有助于按照正确的方式调用它们。余留、可选和关键字参数 是昂贵的。只使用普通参数,函数调用中的参量会被调用者简单的留在被调者能够找到的地方。但其他种类的参数涉及运行时的处理。关键字参数是最差的。针对内置函数,优秀的编译器采用特殊的办法把使用关键字参量的调用编译成快速代码 (fast code)。但对于你自己编写的函数,避免在程序中对速度敏感的部分使用它们只有好处没有坏处。另外,不把大量的参量都放到余留参数中也是明智的举措,如果这可以避免的话。
不同的编译器有时也会有一些它们独到优化。例如,有些编译器可以针对键值是一个狭小范围中的整数的 case
语句进行优化。查看你的用户手册来了解那些实现特有的优化的建议吧。