【发布时间】: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