Common lisp 循环';对数组或列表不加区分 问题
假设您有许多列表或数组,为了举例,假设有两个:Common lisp 循环';对数组或列表不加区分 问题,common-lisp,sbcl,Common Lisp,Sbcl,假设您有许多列表或数组,为了举例,假设有两个: (defparameter *arr* #(1 2 3)) (defparameter *list* '(4 5 6)) 您可以使用交叉或中的关键字在其上循环: (loop for elem across *arr* do (format t "~a" elem)) => 123 (loop for elem in *list* do (format t "~a" elem)) =
(defparameter *arr* #(1 2 3))
(defparameter *list* '(4 5 6))
您可以使用交叉
或中的关键字在其上循环
:
(loop for elem across *arr* do (format t "~a" elem))
=> 123
(loop for elem in *list* do (format t "~a" elem))
=> 456
我希望能够使用相同的语法在这些数组或列表上循环。我正在使用SBCL,执行速度是一个问题
使用作为
这种语法很好,因为不管它的参数是列表
还是数组
,它都可以工作
(loop for elem being the elements of *arr* do (format t "~a" elem))
=> 123
(loop for elem being the elements of *list* do (format t "~a" elem))
=> 456
但它的速度是可怕的。如果我们通过访问100个元素的列表或数组来进行快速比较,请执行以下操作:
(format t "# Test 1.1.1 : Accessing list of doubles with loop 'in': ") (terpri)
(let ((test-list (make-list 100 :initial-element 12.2d0))
(testvar 0d0))
(declare (optimize (speed 3))
(type list test-list)
(type double-float testvar))
(time (dotimes (it 1000000 t) (loop for el in test-list do
(setf testvar (the double-float el))))))
(format t "# Test 1.1.2 : Accessing list of doubles with loop 'elements': ") (terpri)
(let ((test-list (make-list 100 :initial-element 12.2d0))
(testvar 0d0))
(declare (optimize (speed 3))
(type list test-list)
(type double-float testvar))
(time (dotimes (it 1000000 t) (loop for el being the elements of test-list do
(setf testvar (the double-float el))))))
(format t "# Test 1.2.1 : Accessing simple-array of doubles using loop 'across' : ") (terpri)
(let ((test-array (make-array 100 :initial-element 12.2d0 :element-type 'double-float))
(testvar 0d0))
(declare (optimize (speed 3))
(type double-float testvar)
(type simple-array test-array))
(time (dotimes (it 1000000 t) (loop for el across test-array do
(setf testvar (the double-float el))))))
(format t "# Test 1.2.2 : Accessing simple-array of doubles using loop 'elements' : ") (terpri)
(let ((test-array (make-array 100 :initial-element 12.2d0 :element-type 'double-float))
(testvar 0d0))
(declare (optimize (speed 3))
(type double-float testvar)
(type simple-array test-array))
(time (dotimes (it 1000000 t) (loop for el being the elements of test-array do
(setf testvar (the double-float el))))))
它给了我们:
# Test 1.1.1 : Accessing list of doubles with loop 'in':
Evaluation took:
0.124 seconds of real time
0.123487 seconds of total run time (0.123471 user, 0.000016 system)
99.19% CPU
445,008,960 processor cycles
672 bytes consed
# Test 1.1.2 : Accessing list of doubles with loop 'elements':
Evaluation took:
0.843 seconds of real time
0.841639 seconds of total run time (0.841639 user, 0.000000 system)
99.88% CPU
3,034,104,192 processor cycles
0 bytes consed
# Test 1.2.1 : Accessing simple-array of doubles using loop 'across' :
Evaluation took:
0.062 seconds of real time
0.062384 seconds of total run time (0.062384 user, 0.000000 system)
100.00% CPU
224,896,032 processor cycles
0 bytes consed
# Test 1.2.2 : Accessing simple-array of doubles using loop 'elements' :
Evaluation took:
1.555 seconds of real time
1.554472 seconds of total run time (1.541572 user, 0.012900 system)
[ Run times consist of 0.094 seconds GC time, and 1.461 seconds non-GC time. ]
99.94% CPU
5,598,161,100 processor cycles
1,600,032,848 bytes consed
我认为它必须使用elt
accessor?无论如何,速度上的惩罚是不可接受的
试着聪明地使用宏
我写了一些东西来实现我的目标,即对list
和array
使用相同的语法。我认为这不太好,因为它看起来太尴尬了,但在这里:
(defun forbuild (el-sym list-or-array list-or-array-sym)
"Outputs either :
* (for el-sym in list-or-array)
* (for el-sym across list-or-array)
Depending on type of list-or-array.
el-sym : symbol, eg. 'it1
list-or-array : declared, actual data for list or array
list-or-array-sym : symbol name for the table, to avoid writing the data in full
in the 'loop' call using eval.
Example call : (forbuild 'it1 arr 'arr)"
(cond ((typep list-or-array 'array)
`(for ,el-sym across ,list-or-array-sym))
((typep list-or-array 'list)
`(for ,el-sym in ,list-or-array-sym))))
(defun forbuild-l (l-elsyms l-lars l-larsyms)
"forbuild but over lists of things."
(let ((for-list nil)
(list-temp nil))
(loop for elem in l-elsyms
for lar in l-lars
for larsym in l-larsyms do
(setf list-temp (forbuild elem lar larsym))
(loop for word-temp in list-temp do
(push word-temp for-list)))
(nreverse for-list)))
(defun loop-expr (forlist body)
"Creates the expression ready to be evaluated to execute the loop.
forlist : List of symbols to be inserted syntactically. eg.
FOR IT1 ACROSS ARR1 FOR IT2 IN ARR2
body : all the expression after the 'for' clauses in the 'loop'."
`(loop ,@forlist ,@body))
(defmacro looparl (element list-or-array &rest body)
(let ((forlist (gensym)))
`(let ((,forlist (forbuild2-l (quote ,element)
(list ,@list-or-array)
(quote ,list-or-array))))
(loop-expr ,forlist (quote ,body)))))
基本上,我从参数构建正确的循环
语法。此处给出的looparl
版本可以这样调用:
(let ((arr1 #(7 8 9))
(list2 (list 10 11 12)))
(looparl (it1 it2) (arr1 list2) do (format t "~a ~a" it1 it2) (terpri)))
=> (LOOP FOR IT1 ACROSS ARR1
FOR IT2 IN LIST2
DO (FORMAT T "~a ~a" IT1 IT2) (TERPRI))
这个输出表达式的实际计算在本例中被省略,因为它不适用于非全局名称。如果我们在looparl
的末尾加入一个eval:
(defmacro looparl (element list-or-array &rest body)
(let ((forlist (gensym)))
`(let ((,forlist (forbuild2-l (quote ,element)
(list ,@list-or-array)
(quote ,list-or-array))))
(eval (loop-expr ,forlist (quote ,body))))))
在处理全局变量时,我们发现仍然存在速度问题,因为在运行时会发生计算:
(looparl (it1 it2) (*arr* *list*) for it from 100
do (format t "~a ~a ~a" it1 it2 it) (terpri))
=> 1 4 100
2 5 101
3 6 102
(time (dotimes (iter 1000 t) (looparl (it1 it2) (*arr* *list*) for it from 100
do (format t "~a ~a ~a" it1 it2 it) (terpri))))
=> Evaluation took:
1.971 seconds of real time
1.932610 seconds of total run time (1.892329 user, 0.040281 system)
[ Run times consist of 0.097 seconds GC time, and 1.836 seconds non-GC time. ]
98.07% CPU
1,000 forms interpreted
16,000 lambdas converted
7,096,353,696 processor cycles
796,545,680 bytes consed
每个宏一次计算一千次。但是类型肯定是在编译时已知的吗?looparl
中的语法类型非常好,我希望能够在不影响速度的情况下使用它
我读了这张便条
3你可能想知道为什么LOOP不需要不同的介词就不能判断它是在列表上循环还是在向量上循环。这是作为宏的循环的另一个结果:列表或向量的值在运行时之前是未知的,但作为宏的循环必须在编译时生成代码。LOOP的设计者希望它能生成非常高效的代码。为了能够生成高效的代码来循环(比如向量),它需要在编译时知道该值在运行时是向量——因此,需要使用不同的介词
我是不是在胡说八道?您将如何创建一个工作快速的looparl
编辑1:用于
库
谢谢Ehvince给我们的参考资料。for:for
函数中的over
关键字确实正是我所需要的。然而,这些基准确实令人失望:
(let ((test-list (make-list 100 :initial-element 12.2d0))
(testvar 0d0))
(declare (optimize (speed 3))
(type list test-list)
(type double-float testvar))
(time (dotimes (it 1000000 t)
(for:for ((el over test-list))
(setf testvar (the double-float el))))))
(let ((test-array (make-array 100 :initial-element 12.2d0))
(testvar 0d0))
(declare (optimize (speed 3))
(type simple-array test-array)
(type double-float testvar))
(time (dotimes (it 1000000 t)
(for:for ((el over test-array))
(setf testvar (the double-float el))))))
Evaluation took:
4.802 seconds of real time
4.794485 seconds of total run time (4.792492 user, 0.001993 system)
[ Run times consist of 0.010 seconds GC time, and 4.785 seconds non-GC time. ]
99.83% CPU
17,286,934,536 processor cycles
112,017,328 bytes consed
Evaluation took:
6.758 seconds of real time
6.747879 seconds of total run time (6.747879 user, 0.000000 system)
[ Run times consist of 0.004 seconds GC time, and 6.744 seconds non-GC time. ]
99.85% CPU
24,329,311,848 processor cycles
63,995,808 bytes consed
此库在
和
中使用专门关键字的速度与标准循环
的速度相同。但在<代码>结束时速度非常慢
编辑2:map
和etypecase
感谢sds和Rainer Joswig的建议。它确实适用于我只有一个数组/列表可以迭代的简单情况。让我告诉你们我想到的一个用例:我正在实现一个包装器,既作为培训,也在我的工具箱中有我自己的程序。我想从用户列表或数组中不加区别地获取数据,作为序列到管道到gnuplot。这就是为什么我需要能够同时在多个数组/列表上循环+使用优雅的循环子句来表示迭代次数等
在这个用例(gnuplot包装器)中,对于每个“数据块”,我的循环中只有两到三个for
子句,因此我考虑根据手动输入的类型编写每个组合,这是可能的,但非常尴尬。如果我不得不做这样的事情,我会被卡住:
(loop for el1 in list1
for el2 across arr1
for el3 in list2
for el4 in list3
...)
将list-i
和arr-i
作为输入。这个用例的另一个回退计划就是将所有内容转换为数组
我认为,因为它很容易概念化,所以我可以一次性地编写一些灵活快速的东西,但一定有一个原因,为什么它既不在规范中,也不在SBCL特定的代码中。您正在寻找的东西叫做
:
两者
及
打印123
但是,列表和数组是非常不同的,最好提前决定要使用哪一个。Shinmera的库具有通用的迭代器:
(ql:quickload "for")
(for:for ((a over *arr*)
(b over *list*))
(print (list a b)))
;; (1 4)
;; (2 5)
;; (3 6)
它还有“in”和“accross”,所以在开发过程中使用“over”可能会有所帮助,如果需要的话,可以在以后进行细化
我将让您执行基准测试:)对于您可能会执行的琐碎使用
(flet ((do-something (e)
...))
(etypecase foo
(vector (loop for e across foo do (do-something e)))
(list (loop for e in foo do (do-something e))))
运行时类型的分派可能比使用序列抽象的一般迭代构造要快。将数组强制为列表,然后循环,会提供与最初是列表时相同的性能,这不如使用数组时好,但还不如使用元素那么糟糕,它确实具有使用列表或数组而无需额外机器的优点:
(循环x-in(强制数组列表)执行操作)
(ql:quickload "for")
(for:for ((a over *arr*)
(b over *list*))
(print (list a b)))
;; (1 4)
;; (2 5)
;; (3 6)
(flet ((do-something (e)
...))
(etypecase foo
(vector (loop for e across foo do (do-something e)))
(list (loop for e in foo do (do-something e))))