Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are MAPCAR, MAPC, and MAP compiled to similar code when result is ignored?

First question: Is it reasonable to assume that a modern compiler for Common Lisp will usually compile (mapcar #'fn ...) and (map 'list #'fn ...) into the same code as (mapc #'fn ...)? That is, is it reasonable to assume that a compiler will see that the return value is ignored, so that a new list doesn't need to be constructed? e.g. suppose that my source file contains this code:

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

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

Would mapc be more efficient? I usually run SBCL, but my guess is that any good compiler would be able to figure out that there's no need to cons up a new list in this situation. Am I right?

Second question: In the same situation, should I assume that a modern compiler will usually compile map 'list into the same code as mapcar, as long as 'list is there in the source code, and not chosen at run time?

Third question: Similar question for other sequences. For example, if I replace the mapcar line in the progn above with (map 'vector #'set-foo-5 #(a b c)), should I assume that that the compiled code will not bother to construct a new vector?

like image 835
Mars Avatar asked Dec 11 '22 11:12

Mars


2 Answers

First of all, map and mapcar differ in a very important way: the latter operates on lists while the former works with any sequences. This means that (map nil ...) (or (map 'list ...)) is equivalent to (mapc ...) (resp. (mapcar ...)) only if the compiler can prove that all the data arguments are lists.

Second, most modern Lisp compilers (e.g., SBCL) are usually good enough to figure out such things (with declarations, when necessary).

Third, the only way to be sure is to use disassemble.

Fourth, the function choice is a way to document your code. When you use map, you are telling the human reader of your code that you will be passing non-lists to the function. If you are sure that only lists will be used, why would you want to confuse the reader (yourself a few months later)?

Fifth, premature optimization is the root of all evil.

like image 183
sds Avatar answered Jun 07 '23 11:06

sds


Using some of the tips in @sds's answer, I realized that there are simple preliminary tests that might answer my question for a specific implementation (without having to work through dissemble output). It appears that SBCL and CCL do not necessarily treat mapcar the same way as mapc when mapcar's return value is ignored. First define lis1 and lis2 as lists of significant length (mine were length 100,000, and contained integers), then run mapcar, mapc, etc. over them many times, as in the the following (with optional preliminary calls to gc to clear out old garbage):

(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~%")))

For example, SBCL on my machine produces:

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

These are typical results with default optimization settings. Using declaim to optimize for speed, non-safety, etc. speeds things up a little bit but doesn't change the fact that mapc and map nil are an order of magnitude faster than mapcar, and that mapcar does a lot of consing. Results for CCL are similar, but slower overall.

like image 32
Mars Avatar answered Jun 07 '23 12:06

Mars