Common lisp 向通用Lisp编译器保证算术表达式的结果是fixnum的最简单方法是什么?
我想告诉sbcl,以下函数将仅使用fixnum值调用,其结果符合fixnum:Common lisp 向通用Lisp编译器保证算术表达式的结果是fixnum的最简单方法是什么?,common-lisp,Common Lisp,我想告诉sbcl,以下函数将仅使用fixnum值调用,其结果符合fixnum: (defun layer (x y z n) (+ (* 2 (+ (* x y) (* y z) (* x z))) (* 4 (+ x y z n -2) (1- n)))) 我的第一次尝试是 (defun layer (x y z n) (declare (fixnum x y z n)) (the fixnum (+ (* 2 (+ (* x y) (* y z) (* x z
(defun layer (x y z n)
(+ (* 2 (+ (* x y) (* y z) (* x z)))
(* 4 (+ x y z n -2) (1- n))))
我的第一次尝试是
(defun layer (x y z n)
(declare (fixnum x y z n))
(the fixnum
(+ (* 2 (+ (* x y) (* y z) (* x z)))
(* 4 (+ x y z n -2) (1- n))))
但这个返回类型声明并不能保证所有中间结果都是fixnums,我通过查看sbcl生成的非常有用的编译注释发现了这一点。然后我做了这个:
(defmacro fixnum+ (&rest args)
(reduce
(lambda (x y) `(the fixnum (+ ,x ,y)))
args))
(defmacro fixnum* (&rest args)
(reduce
(lambda (x y) `(the fixnum (* ,x ,y)))
args))
(defun layer (x y z n)
(declare (fixnum x y z n))
(fixnum+ (fixnum* 2 (fixnum+ (fixnum* x y) (fixnum* y z) (fixnum* x z)))
(fixnum* 4 (fixnum+ x y z n -2) (the fixnum (1- n)))))
而且效果很好。我的问题是:有没有更简单、更惯用的方法
例如,也许我可以重新声明+、-、*、1-的类型以保证fixnum结果?(我知道这通常是一个坏主意,但我可能希望在某些程序中这样做。)CHICKEN scheme有
(declare(fixnum算术))
可以实现我想要的功能:它(不安全地)假设fixnums上所有算术运算的结果都是fixnums。您可以使用声明函数的类型
例如:
(defun foo (a b)
(declare (ftype (function (&rest fixnum) fixnum) + * 1-)
(type fixnum a b)
(inline + * 1-)
(optimize (speed 3) (safety 0) (debug 0) (space 0)))
(+ a (* a (1- b))))
这有区别吗?试试这个:
(defun layer (x y z n)
(declare (optimize speed) (fixnum x y z n))
(logand most-positive-fixnum
(+ (* 2 (+ (* x y) (* y z) (* x z)))
(* 4 (+ x y z n -2) (1- n)))))
看
编辑:
如评论中所述,SBCL-1.1.9(或更高版本)是这项工作所必需的。此外,通过内联子程序,还可以节省约40%的时间:
;;; From: https://gist.github.com/oantolin/6073417
(declaim (optimize (speed 3) (safety 0)))
(defmacro with-type (type expr)
(if (atom expr)
expr
(let ((op (car expr)))
(reduce
(lambda (x y)
`(the ,type
(,op ,@(if x (list x) '())
(with-type ,type ,y))))
(cdr expr)
:initial-value nil))))
(defun layer (x y z n)
(declare (fixnum x y z n))
(with-type fixnum
(+ (* 2 (+ (* x y) (* y z) (* x z)))
(* 4 (+ x y z n -2) (1- n)))))
(defun cubes (n)
(declare (fixnum n))
(let ((count (make-array (+ n 1) :element-type 'fixnum)))
(loop for x of-type fixnum from 1 while (<= (layer x x x 1) n) do
(loop for y of-type fixnum from x while (<= (layer x y y 1) n) do
(loop for z of-type fixnum from y while (<= (layer x y z 1) n) do
(loop for k of-type fixnum from 1 while (<= (layer x y z k) n) do
(incf (elt count (layer x y z k)))))))
count))
(defun first-time (x)
(declare (fixnum x))
(loop for n of-type fixnum = 1000 then (* 2 n)
for k = (position x (cubes n))
until k
finally (return k)))
;;; With modarith and inlining
(defun first-time/inline (x)
(declare (fixnum x))
(labels
((layer (x y z n)
(logand #.(1- (ash 1 (integer-length most-positive-fixnum)))
(+ (* 2 (+ (* x y) (* y z) (* x z)))
(* 4 (+ x y z n -2) (1- n)))))
(cubes (n)
(let ((count (make-array (+ n 1) :element-type 'fixnum)))
(loop for x of-type fixnum from 1 while (<= (layer x x x 1) n) do
(loop for y of-type fixnum from x while (<= (layer x y y 1) n) do
(loop for z of-type fixnum from y while (<= (layer x y z 1) n) do
(loop for k of-type fixnum from 1 while (<= (layer x y z k) n)
do (incf (elt count (layer x y z k)))))))
count)))
(declare (inline layer cubes))
(loop for n of-type fixnum = 1000 then (* 2 n)
thereis (position x (cubes n)))))
#+(or)
(progn
(time (print (first-time 1000)))
(time (print (first-time/inline 1000))))
;; 18522
;; Evaluation took:
;; 0.448 seconds of real time
;; 0.448028 seconds of total run time (0.448028 user, 0.000000 system)
;; 100.00% CPU
;; 1,339,234,815 processor cycles
;; 401,840 bytes consed
;;
;;
;; 18522
;; Evaluation took:
;; 0.259 seconds of real time
;; 0.260016 seconds of total run time (0.260016 user, 0.000000 system)
;; 100.39% CPU
;; 776,585,475 processor cycles
;; 381,024 bytes consed
;;;发件人:https://gist.github.com/oantolin/6073417
(说教(优化(速度3)(安全0)))
(带有类型(类型expr)的defmacro)
(如果(原子表达式)
expr
(出租(op(汽车出口)))
(减少
(λ(x y)
`(表格,类型)
(,op,@(如果x(列表x)'))
(带type,type,y)))
(cdr expr)
:初始值(零)
(除雾层(x y z n)
(声明(固定数量x y z n))
(带有fixnum类型)
(+(*2(+(*XY)(*YZ)(*XZ)))
(*4(+xyzn-2)(1-n())))
(defun立方体(n)
(声明(fixnum n))
(let((计数(生成数组(+n1):元素类型'fixnum)))
(fixnum类型的x从1开始循环,而(在他的书ANSI Common Lisp中,Paul Graham使用类型
显示了宏,该宏将表达式及其所有子表达式包装在表单中,同时确保正确处理给定两个以上参数的运算符
例如,(使用fixnum(+1 2 3)类型)
将扩展到表单
(the fixnum (+ (the fixnum (+ (the fixnum 1) (the fixnum 2)))
(the fixnum 3))
带有辅助函数的宏的代码为
(defmacro with-type (type expr)
`(the ,type ,(if (atom expr)
expr
(expand-call type (binarize expr)))))
(defun expand-call (type expr)
`(,(car expr) ,@(mapcar #'(lambda (a)
`(with-type ,type ,a))
(cdr expr))))
(defun binarize (expr)
(if (and (nthcdr 3 expr)
(member (car expr) '(+ - * /)))
(destructuring-bind (op a1 a2 . rest) expr
(binarize `(,op (,op ,a1 ,a2) ,@rest)))
expr))
中的书中的代码链接可在找到
该法典中的一条评论指出,“该法典由Paul Graham于1995年版权所有,但任何想要
使用它是免费的。”声明层函数内联会导致更快的速度,即使在块编译打开时也是如此
在我的Apple Air M1上,层内联,块编译在Arm64版本的SBCL 2.1.2下以0.06秒的速度运行
CL-USER> (time (first-time 1000))
Evaluation took:
0.060 seconds of real time
0.060558 seconds of total run time (0.060121 user, 0.000437 system)
101.67% CPU
303,456 bytes consed
我刚刚记得在多维数据集中声明count数组也会有所帮助
(声明(类型(简单数组fixnum(*)计数))
如果不内联层函数,时间大约为0.2秒
CL-USER> (time (first-time 1000))
Evaluation took:
0.201 seconds of real time
0.201049 seconds of total run time (0.200497 user, 0.000552 system)
100.00% CPU
251,488 bytes consed
或者将图层功能转换为宏会使其速度更快
(defmacro layer (x y z n)
(declare (fixnum x y z n))
`(logand #.(1- (ash 1 (integer-length most-positive-fixnum)))
(+ (* 2 (+ (* ,x ,y) (* ,y ,z) (* ,x ,z)))
(* 4 (+ ,x ,y ,z ,n -2) (1- ,n)))))
CL-USER> (time (first-time 1000))
Evaluation took:
0.047 seconds of real time
0.047032 seconds of total run time (0.046854 user, 0.000178 system)
100.00% CPU
312,576 bytes consed
以普通基准测试为基准,平均运行时间仅为0.04秒:
CL-USER> (benchmark:with-timing (100) (first-time 1000))
- SAMPLES TOTAL MINIMUM MAXIMUM MEDIAN AVERAGE DEVIATION
REAL-TIME 100 3.985173 0.039528 0.06012 0.039595 0.039852 0.002046
RUN-TIME 100 3.985848 0.039534 0.06014 0.039605 0.039858 0.002048
USER-RUN-TIME 100 3.975407 0.039466 0.059829 0.039519 0.039754 0.002026
SYSTEM-RUN-TIME 100 0.010469 0.00005 0.000305 0.000088 0.000105 0.00005
PAGE-FAULTS 100 0 0 0 0 0 0.0
GC-RUN-TIME 100 0 0 0 0 0 0.0
BYTES-CONSED 100 50200736 273056 504320 504320 502007.38 23010.477
EVAL-CALLS 100 0 0 0 0 0 0.0
这完全破坏了性能!我使用layer函数的程序运行时间为1.7秒,根本没有类型声明;对于我在问题中提到的声明(使用fixnum+
宏),它运行时间为0.36秒;但是根据这个建议,它运行时间为15秒!(这不是打字错误)。编译说明中充满了“强制执行泛型-+”、“强制执行泛型-*”等。您链接到的文档表明了一个可能的罪魁祸首:优化不适用于*(在文档术语中,*不是一个“好”函数)。您使用的是哪个版本的SBCL?对于1.1.9,我在编译时没有看到任何性能说明。请参阅编辑。如果您可以隔离代码以生成基准,那将非常好。此要点包含我正在运行的实际代码。它当前使用您的函数版本,并在我的计算机上运行15秒(使用SBCL 1.1.8)。要测试将每个操作包装在(fixnum…
中的解决方案,请使用logand
注释该行,并取消对上面的行的注释;该行在我的计算机上运行时间为0.3秒。(顺便说一下,在此要点中,我将我的fixnum+
和fixnum*
宏替换为(我的版本)Paul Graham的with type
宏,正如@TerjeD所建议的那样。它在性能上没有差别,但使代码看起来更漂亮。)好奇。在我的机器上,使用logand的版本仍然比使用type的版本略好:也许SBCL在最新版本中做了一些改进。我尝试了(ftype(function(&rest-fixnum)fixnum)+*1-
并且编译器抱怨COMMON-LISP包被锁定;因此我添加了(eval-when(:compile-top-level:execute)(解锁包'cl))
然后它编译得很干净,但是性能被破坏了!我的原始程序在1.7秒内运行,没有任何类型声明;使用我在问题中提到的声明(使用fixnum+
宏),它的运行时间为0.36秒;但根据此建议,它的运行时间为5.8秒!是的!这起作用了,它提供了与“在中包装所有内容”(fixnum…
"我开始使用的策略。如果没有inline
声明,它将在我提到的6秒钟内运行,这比删除所有类型声明要慢得多……感谢您的帮助!鉴于这种方法需要解锁COMMON-LISP包以及ftype和inline声明,我想我可能会使用Paul Graham的方法用类型
宏包装所有中间结果的(类型…
)的想法非常有启发性。但无论如何,谢谢你,这非常有启发性。哇,我从来没有见过像这样使用内联!干杯!这绝对比我拥有的界面好(但基本上是相同的解决方案)