【问题标题】:relative performance: lisp map vs loop functions相对性能:lisp map 与循环函数
【发布时间】:2014-09-10 23:23:11
【问题描述】:

我正在测试 common 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 函数比循环慢 1.5 倍,尽管它看起来不是一个非常线性的关系。我找不到任何关于 SBCL 的 map 实现的有用文档,所以我不知道幕后发生了什么来使一个或多或少更快。我会假设它来自 map 不断调用一个单独的函数,但是由于该函数是内联的,所以不应该有任何开销吗?循环中存在哪些优化,而地图中没有?我可以执行更多优化来加速此代码吗?

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

【问题讨论】:

  • 这个实现不是特定的,而不是常见的 lisp 问题吗?
  • 最后一段似乎暗示了这一点,但其目的只是为了更好地理解 map vs loop 的实际底层性质,而不是编译器为它们生成的实际汇编指令。我假设它们在编译器中的实现方式与标准函数类似,至少在较高级别(显然较低级别的细节会有所不同)。
  • 我找不到任何关于 SBCL 的地图实现的有用文档 SBCL 是开源的,您可以轻松查看它的实现。你可以从the definition of MAP in seq.lisp开始。
  • 而 LOOP 是一个宏,所以如果你 (pprint (macroexpand '(loop …))),你也可以很好地了解那里发生的事情。
  • 别忘了可以使用DISASSEMBLE函数反汇编代码。

标签: performance optimization lisp common-lisp sbcl


【解决方案1】:

您在这两者之间进行了不公平的比较,因为您试图以迭代方式而不是按照定义的功能方式制作地图。因此,您在 map 中与全局变量和突变有很多交互,这将产生依赖于实现的结果,因为不能保证 map 的元素将按从前到后的顺序进行评估。这是 map 未优化以支持的用法,因此它的性能可能更差也就不足为奇了。

尝试与这些函数进行比较:

(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 中运行它时,我发现地图的性能确实优于循环。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-02-18
    • 2015-08-28
    • 1970-01-01
    • 2013-07-18
    • 2021-11-18
    • 2017-05-22
    • 1970-01-01
    • 2019-12-10
    相关资源
    最近更新 更多