相对性能:lisp map vs循环函数

时间:2014-07-20 07:43:57

标签: performance optimization lisp common-lisp sbcl

我正在测试普通的lisp的map或loop在执行相同的操作时是否执行类似的操作。代码从输入字符序列读取,并且根据每次迭代翻转的布尔值,从该序列写入另一个序列。代码如下(这不是生产代码,这只是为了测试这两个代码):

(defvar test-in)
(defvar test-out)
(defvar bool)
(setf test-in (make-sequence 'string (* (expt 10 6) 5) :initial-element #\A))
(setf test-out (make-sequence 'string (* (expt 10 6) 5) :initial-element #\B))
(setf bool nil)
(defun test-loop ()
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (string test-in test-out) (boolean bool))
  (loop for index fixnum from 0 to (- (length test-in) 1)
     do (progn
          (if bool
              (setf (aref test-out index) (aref test-in index)))
          (setf bool (not bool)))))

(defvar cur-count)
(declaim (inline map-fun))
(defun map-fun (char)
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (character char) (fixnum cur-count) (string test-out))
  (if bool
      (setf (aref test-out cur-count) char))
  (setf bool (not bool))
  (the fixnum (incf cur-count)))

(defun test-map ()
  (declare (optimize (speed 3) (safety 0) (debug 0)))
  (declare (string test-in))
  (declare (inline map-fun))
  (setf cur-count 0)
  (map nil #'map-fun test-in)
  (setf cur-count 0))

当我描述它们时,我得到:

CL-USER> (time (test-loop))
Evaluation took:
  0.110 seconds of real time
  0.110000 seconds of total run time (0.110000 user, 0.000000 system)
  100.00% CPU
  175,227,978 processor cycles
  0 bytes consed
CL-USER> (time (test-map))
Evaluation took:
  0.153 seconds of real time
  0.150000 seconds of total run time (0.150000 user, 0.000000 system)
  98.04% CPU
  243,006,100 processor cycles
  0 bytes consed

对于长度为(*(expt 10 6)15)的字符串,现在的表现是:

CL-USER> (time (test-loop))
Evaluation took:
  0.353 seconds of real time
  0.353333 seconds of total run time (0.353333 user, 0.000000 system)
  100.00% CPU
  562,929,132 processor cycles
  0 bytes consed
CL-USER> (time (test-map))
Evaluation took:
  0.475 seconds of real time
  0.473334 seconds of total run time (0.473334 user, 0.000000 system)
  99.58% CPU
  757,221,636 processor cycles
  0 bytes consed

首先,很明显看到循环执行的操作数量减少了很多。看起来map函数比loop慢1.5倍,尽管它看起来不像是一个非常线性的关系。我无法在SBCL的地图实现上找到任何有用的文档,因此我不知道幕后发生了什么或多或少的速度。我会假设它是从map不断调用一个单独的函数,但由于该函数是内联的,不应该有任何开销?循环中存在哪种优化不在映射中?我可以执行更多优化来加速此代码吗?

更新:我在REPL中分配了更多的字符串,我的动态内存被淹没了。然后,当我调用(test-map)时,我收到了“未处理的内存故障”,我认为这是内存分配的结果。运行完成时(test-loop)运行时不会发生这种情况,因此更加混乱:我认为这意味着测试图以某种方式分配内存。它会从哪里来?

1 个答案:

答案 0 :(得分:2)

你正在设置这两者之间的不公平比较,因为你试图按照定义迭代地而不是功能地制作地图。因此,您在地图中与全局变量和变异进行了大量交互,这将产生依赖于实现的结果,因为无法保证地图元素将按照从头到尾的顺序进行评估。这是一种用于地图未经过优化以支持的用法,因此它可能表现得更差也就不足为奇了。

尝试与这些功能进行比较:

(defun test-map (n)
  (map 'array #'sqrt (make-sequence 'array n :initial-element 4)))

(defun test-loop (n)
  (let ((out (make-sequence 'array n))
        (in (make-sequence 'array n :initial-element 4))) 
   (loop for index fixnum from 0 to (- n 1)
     do (setf (aref out index) (sqrt (aref in index))))
   out))

我认为这是一个更公平的比较,当我在Allegro Lisp中运行时,我发现地图确实胜过了循环。