4

第一个问题:假设 Common Lisp 的现代编译器通常会编译(mapcar #'fn ...)(map 'list #'fn ...)相同的代码是否合理(mapc #'fn ...)?也就是说,假设编译器会看到返回值被忽略,因此不需要构造新列表是否合理?例如,假设我的源文件包含以下代码:

(defun set-foo-5 (sym)
  (setf (get sym 'foo) 5))

(progn 
  (mapcar #'set-foo-5 '(a b c))
  (format t "All foos are five!~%"))

mapc更有效率吗?我通常运行 SBCL,但我的猜测是任何好的编译器都能够发现在这种情况下没有必要创建一个新的列表。我对吗?

第二个问题:在同样的情况下,我是否应该假设现代编译器通常会编译map 'list成与 相同的代码mapcar,只要'list在源代码中存在,而不是在运行时选择?

第三个问题:其他序列的类似问题。例如,如果我将mapcar上面 progn 中的行替换为 ,我(map 'vector #'set-foo-5 #(a b c))是否应该假设编译后的代码不会费心构造一个新的向量?

4

2 回答 2

10

首先,有一个非常重要mapmapcar区别:后者对列表进行操作,而前者对任何序列都有效。这意味着(map nil ...)(or )仅当编译器可以证明所有数据参数都是s时才(map 'list ...)等效于(mapc ...)(resp. )。(mapcar ...)list

其次,大多数现代 Lisp 编译器(例如SBCL)通常足以解决这些问题(在必要时使用声明)。

第三,唯一确定的方法就是使用disassemble

第四,函数选择是记录你的代码的一种方式。当您使用 时map,您是在告诉代码的人类读者您将向函数传递非列表。如果您确定只会使用列表,为什么要混淆读者(几个月后您自己)?

第五过早的优化是万恶之源

于 2013-09-29T00:56:47.810 回答
3

使用@sds 答案中的一些技巧,我意识到有一些简单的初步测试可以回答我针对特定实现的问题(无需通过dissemble输出)。mapcar看起来 SBCL 和 CCL 的处理方式不一定与忽略 ' 的返回值mapc时相同。mapcar首先将lis1和定义lis2为重要长度的列表(我的长度为 100,000,并且包含整数),然后在它们上运行mapcar,mapc等多次,如下所示(可选的初步调用gc以清除旧垃圾):

(gc :full t)
(time 
  (progn
    (dotimes (ignored 1000)
      (mapcar #'+ lis1 lis2))
    (format t "mapcar:~%")))

(gc :full t)
(time 
  (progn
    (dotimes (ignored 1000)
      (mapc #'+ lis1 lis2))
    (format t "mapc:~%")))

(gc :full t)
(time 
  (progn
    (dotimes (ignored 1000)
      (map nil #'+ (the list lis1) (the list lis2)))
    (format t "map nil with lists~%")))

例如,我机器上的 SBCL 产生:

mapcar:
Evaluation took:
  2.306 seconds of real time
  2.287627 seconds of total run time (2.136130 user, 0.151497 system)
  [ Run times consist of 0.147 seconds GC time, and 2.141 seconds non-GC time. ]
  99.22% CPU
  3,683,188,504 processor cycles
  1,600,049,536 bytes consed

mapc:
Evaluation took:
  0.639 seconds of real time
  0.638733 seconds of total run time (0.638011 user, 0.000722 system)
  100.00% CPU
  1,020,310,296 processor cycles
  0 bytes consed

map nil with lists
Evaluation took:
  0.592 seconds of real time
  0.592114 seconds of total run time (0.591199 user, 0.000915 system)
  100.00% CPU
  945,957,944 processor cycles
  0 bytes consed

这些是使用默认优化设置的典型结果。使用declaim来优化速度、非安全性等可以稍微加快速度,但不会改变mapcandmap nilmapcar, 快一个数量级的事实,这mapcar会带来很多麻烦。CCL 的结果相似,但总体速度较慢。

于 2013-09-29T06:38:21.653 回答