【问题标题】:Efficient vector operations of linear algebra in Common Lisp, especially SBCL?Common Lisp中线性代数的高效向量运算,尤其是SBCL?
【发布时间】:2011-12-02 12:43:17
【问题描述】:

下面的程序似乎效率很低。与 SBCL 1.0.53 相比,它需要 28.980 秒的 GC 时间,而非 GC 时间为 6.361 秒。

(deftype vec3 () '(simple-array double-float (3)))

(declaim (inline make-vec3 vec3-zero
             vec3-x vec3-y vec3-z
             vec3-+))

(defun make-vec3 (x y z)
  (declare (optimize (speed 3) (safety 0)))
  (make-array 3 :element-type 'double-float
                :initial-contents (list x y z)))

(defun vec3-zero ()
  (make-vec3 0.0d0 0.0d0 0.0d0))

(defun vec3-x (x)
  (declare (optimize (speed 3) (safety 0)))
  (declare (type (simple-array double-float (3)) x))
  (aref x 0))

(defun vec3-y (x)
  (declare (optimize (speed 3) (safety 0)))
  (declare (type (simple-array double-float (3)) x))
  (aref x 1))

(defun vec3-z (x)
  (declare (optimize (speed 3) (safety 0)))
  (declare (type (simple-array double-float (3)) x))
  (aref x 2))

(defun vec3-+ (a b)
  (declare (optimize (speed 3) (safety 0)))
  (make-vec3 (+ (vec3-x a) (vec3-x b))
             (+ (vec3-y a) (vec3-y b))
             (+ (vec3-z a) (vec3-z b))))


;; main

(defun image (x y)
  (make-array (* x y) :element-type 'vec3 :initial-element (vec3-zero)))

(defun add (to from val)
  (declare (type (simple-array vec3 (*)) to from)
           (type vec3 val)
           (optimize (speed 3) (safety 0)))
  (let ((size (array-dimension to 0)))
    (dotimes (i size)
      (setf (aref to i) (vec3-+ (aref from i) val)))))

(defun main ()
  (let ((to (image 800 800))
        (x (make-vec3 1.0d0 1.0d0 1.0d0)))
    (time (dotimes (i 200)
            (add to to x)))
    (print (aref to 0))))

时间:

* (main)
Evaluation took:
  39.530 seconds of real time
  35.340237 seconds of total run time (25.945526 user, 9.394711 system)
  [ Run times consist of 28.980 seconds GC time, and 6.361 seconds non-GC time. ]
  89.40% CPU
  83,778,297,762 processor cycles
  46 page faults
  6,144,014,656 bytes consed


#(200.0d0 200.0d0 200.0d0) 
#(200.0d0 200.0d0 200.0d0)

是否有任何方法可以更有效地计算它,同时保持 vec3 抽象?

例如,使用宏实现 Worker/Wrapper 转换可以消除 vec3 的 conses。

作为另一种方式,为 vec3 制作 cons pool 将减少内存分配。

理想情况下,SBCL 支持某些数据结构(如 vec3)作为数组元素的非描述符表示会很好。

【问题讨论】:

  • 也许在这里您可以找到提高性能的提示:random-state.net/log/3530433886.html
  • 谢谢。它不是像 vec3 这样的结构化数据,而是平面浮点运算。
  • 您是否与defstruct 实现进行了比较?

标签: performance common-lisp sbcl


【解决方案1】:

我认为在这些情况下,使用宏可能是个好主意。接下来,我总是犹豫声明(安全 0),它带来了非常非常轻微的性能提升,并且可能导致奇怪的行为,如果只有 defun 中的代码,而且所有调用 defun 的代码都不是绝对正确的。

我认为重要的是不要在 make-vec3 中创建新的列表对象。我附上了您的代码的一些快速而肮脏的优化。在我的机器上原始代码 运行在

; cpu time (non-gc) 27.487818 sec user, 0.008999 sec system
; cpu time (gc)     17.334368 sec user, 0.001999 sec system
; cpu time (total)  44.822186 sec user, 0.010998 sec system
; real time  44.839858 sec
; space allocation:
;  0 cons cells, 45,056,000,000 other bytes, 0 static bytes

我的版本在

中运行
; cpu time (non-gc) 4.075385 sec user, 0.001000 sec system
; cpu time (gc)     2.162666 sec user, 0.000000 sec system
; cpu time (total)  6.238051 sec user, 0.001000 sec system
; real time  6.240055 sec
; space allocation:
;  8 cons cells, 8,192,030,976 other bytes, 0 static bytes

这是使用 Allegro。 YMMV 在其他 lisps 上。您提到为 vec3 数组池化 conses/memory,我认为重用这些对象,即破坏性地修改它们,是一个好主意,当您有机会这样做时。在我的 lisp 中,一个 vec3 占用 64 个字节,这是相当多的……另一个有用的事情当然是调用分析器来查看时间花在了哪里。此外,在这些数学繁重的问题中,数组引用和算术尽可能地开放编码是很重要的。大多数 lisp 可以 (dissassemble 'my-function),这可以很好地了解这些操作是否确实是开放编码的,或者是否调用了运行时。

(deftype vec3 () '(simple-array double-float (3)))

(declaim (optimize (speed 3) (debug 0) (safety 1)))

(defmacro make-vec3 (x y z)
  `(let ((vec3 
     (make-array 3 :element-type 'double-float :initial-element 0.0d0)))
   (setf (aref vec3 0) ,x
         (aref vec3 1) ,y
         (aref vec3 2) ,z)
     vec3))


(defun vec3-zero ()
  (make-vec3 0.0d0 0.0d0 0.0d0))

(defmacro vec3-x (x)
  `(aref ,x 0))

(defmacro vec3-y (x)
  `(aref ,x 1))

(defmacro vec3-z (x)
  `(aref ,x 2))

(defun vec3-+ (a b)
  (declare (type vec3 a b))
  (make-vec3 (+ (vec3-x a) (vec3-x b))
             (+ (vec3-y a) (vec3-y b))
             (+ (vec3-z a) (vec3-z b))))

(defun image (x y)
  (make-array (* x y) :element-type 'vec3 :initial-element (vec3-zero)))

(defun add (to from val)
  (declare (type (simple-array vec3 (*)) to from)
           (type vec3 val))
  (let ((size (array-dimension to 0)))
    (dotimes (i size)
      (setf (aref to i) (vec3-+ (aref from i) val)))))

(defun main ()
  (let ((to (image 800 800))
        (x (make-vec3 1.0d0 1.0d0 1.0d0)))
    (time (dotimes (i 200)
            (add to to x)))
    (print (aref to 0))))

【讨论】:

  • 谢谢,但您的代码在我的环境中似乎没有提高性能。反汇编 add 函数,我的代码是开放编码的,不会调用运行时。正如您所说,我将尝试使用宏,这可能是一个好主意。反正我很羡慕你能用 Allegro。
  • 我认为 SBCL 在优化数字代码方面比 Allegro 更具侵略性。 Allegro 可以获得相同的性能,但需要更多的类型声明。我没有尝试过的另一件事可能会(或不会)在一定程度上提高性能是使 vec3 对象 conses 而不是数组。
  • 如您所说,SBCL 在计算直接编码为双浮点运算的 x、y 和 z 元素时非常有效,无需 vec3 数据抽象。所以,我怀疑我的环境和你的环境性能差异是由make-vec3函数/宏中make-array的内存分配引起的。我将尝试使用 conses 而不是数组。
  • 您还可以考虑制作 vec3-+ 的破坏性版本,重用其中一个参数。确保也更改 image() 中的 :initial-element!
  • CL 和 C 中的性能差异不是由“addsd”操作码引起的,而是由 vec3 数组的引用引起的,其中需要两个引用才能获取我的 vec3 数组的双浮点元素代码,如“array -> vec3 -> double-float”。使用具有 x3 元素的双浮点数组,而不是 vec3 数组,以及适当的宏抽象,我获得了与 C 一样快的性能改进。
猜你喜欢
  • 2016-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 2015-09-30
  • 1970-01-01
相关资源
最近更新 更多