我正在测试普通的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)
运行时不会发生这种情况,因此更加混乱:我认为这意味着测试图以某种方式分配内存。它会从哪里来?
答案 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中运行时,我发现地图确实胜过了循环。