当结果被忽略时,MAPCAR、MAPC 和 MAP 是否编译为类似的代码



第一个问题:假设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,而不是在运行时选择?

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

首先mapmapcar有一个非常重要的区别:后者在列表上运行,而前者适用于任何序列。 这意味着(map nil ...)(或(map 'list ...)(等同于(mapc ...)(或 (mapcar ...) ( 仅当编译器能够证明所有数据参数都是 list s。

其次,大多数现代Lisp编译器(例如SBCL(通常足以弄清楚这些事情(必要时使用声明(。

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

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

第五,过早优化是万恶之源。

使用 @sds 答案中的一些提示,我意识到有简单的初步测试可以回答我对特定实现的问题(无需完成dissemble输出(。 看来SBCL和CCL不一定像mapc一样对待mapcar,当mapcar的返回值被忽略时。 首先将 lis1lis2 定义为长度很长的列表(我的长度为 100,000,包含整数(,然后在它们上运行 mapcarmapc 等多次,如下所示(可选择初步调用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来优化速度、非安全性等可以加快速度,但并没有改变这样一个事实,即mapcmap nilmapcar快一个数量级,而且mapcar做了很多事情。 CCL 的结果相似,但总体速度较慢。

相关内容

  • 没有找到相关文章

最新更新