Эффективные векторные операции линейной алгебры в Common Lisp, особенно SBCL?

Программа ниже кажется очень неэффективной. Это занимает до 28,980 времени GC, в отличие от 6,361 секунды времени без GC, с SBCL 1.0.53.

(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.

С другой стороны, создание пула cons для vec3 уменьшит выделение памяти.

В идеале было бы неплохо, чтобы SBCL поддерживал недескрипторные представления для некоторых структур данных, таких как vec3, в виде элементов массива.


person masayuki takagi    schedule 02.12.2011    source источник
comment
Возможно, здесь вы сможете найти советы по повышению производительности: random-state.net/log/ 3530433886.html   -  person Vsevolod Dyomkin    schedule 03.12.2011
comment
Спасибо. Речь идет не о структурированных данных, таких как vec3, а о плоских операциях с плавающей запятой.   -  person masayuki takagi    schedule 05.12.2011
comment
Вы сравнивали с defstruct реализацией?   -  person Justin Meiners    schedule 19.01.2020


Ответы (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

Это использование Аллегро. YMMV на других языках. Вы упомянули объединение conses/memory для массивов vec3, и я думаю, что повторное использование этих объектов, т. е. деструктивное их изменение, является хорошей идеей, когда у вас есть возможность сделать это. На моем лиспе vec3 занимает 64 байта, что совсем немного... Еще одна полезная вещь, это, конечно, вызвать профилировщик, чтобы посмотреть, на что потрачено время. Кроме того, в этих сложных математических задачах важно, чтобы ссылки на массивы и арифметические операции были как можно более открытыми. Большинство лиспов могут (дизассемблировать «мою-функцию»), что дает хорошее представление о том, действительно ли эти операции были закодированы в открытом коде или вызывается среда выполнения.

(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))))
person Community    schedule 02.12.2011
comment
Спасибо, но ваш код, похоже, не улучшает производительность в моей среде. Разобрав функцию добавления, мой код хорошо открыт и не вызывает среду выполнения. Я постараюсь использовать макросы, как вы говорите, это может быть хорошей идеей. Во всяком случае, я завидую, что вы можете использовать Аллегро. - person masayuki takagi; 04.12.2011
comment
Я думаю, что SBCL гораздо более агрессивен, когда дело доходит до оптимизации числового кода, чем Allegro. Allegro может обеспечить ту же производительность, но требует гораздо большего количества объявлений типов. Еще одна вещь, которую я не пробовал, которая может (или нет) несколько улучшить производительность, - это сделать объекты vec3 conses вместо массивов. - person ; 04.12.2011
comment
Как вы говорите, SBCL работает очень эффективно при вычислении элементов x, y и z, закодированных непосредственно как операции с двойным числом с плавающей запятой, без абстракции данных vec3. Итак, я подозреваю, что разница в производительности моей среды и вашей вызвана выделением памяти make-array в функции/макросе make-vec3. Я постараюсь использовать минусы вместо массивов. - person masayuki takagi; 05.12.2011
comment
Вы также можете рассмотреть возможность создания деструктивной версии vec3-+, повторно используя один из ее аргументов. Не забудьте также изменить :initial-element в image()! - person ; 06.12.2011
comment
Ok! У меня уже есть деструктивная версия vec3-+ с исправлением :initial-element в image(), как вы указываете, и она работает очень эффективно. Он работает в 1/2 раза быстрее, чем аналогичный код на C, скомпилированный с помощью gcc, а разница в производительности на CL и C, по-видимому, вызвана некоторыми небольшими различиями в машинных кодах, выполняющих сложение с кодом операции addd, сгенерированным SBCL и gcc. , который я проверил с помощью функции дизассемблирования SBCL и параметра gcc -S. - person masayuki takagi; 06.12.2011
comment
Разница в производительности в CL и C вызвана не опкодом addd, а ссылками массивов vec3, в которых две ссылки нужны для получения элементов массива vec3 типа double-float в моем коде, как array -> vec3 -> double -плавать. Используя массив double-float, который имеет элементы x3, вместо массива vec3, с соответствующей абстракцией макросов, я получил хорошее улучшение производительности так же быстро, как C. - person masayuki takagi; 07.12.2011
comment
@JohanBenumEvensberget: я видел термин «открытый код» в разных местах. Однако я не знаю определения? Можете ли вы предоставить один или ссылку на один? Спасибо. - person Faheem Mitha; 04.01.2013
comment
@masayukitakagi Мне не совсем понятно, что вы имеете в виду под использованием массива с двойным числом с плавающей запятой, который имеет элементы x3, вместо массива vec3 с соответствующей абстракцией макросов. Не могли бы вы обновить свой вопрос или, возможно, ответить на него самостоятельно, используя обновленный, улучшенный код? Мне было бы интересно. Спасибо. - person Faheem Mitha; 04.01.2013
comment
@FaheemMitha Я имею в виду, что если я хочу использовать 100 векторов, каждый из которых содержит элементы x, y и z, я могу использовать массив, содержащий 300 элементов с двойным числом с плавающей запятой, а затем сделать абстракцию для работы, например, с элементом x N-го элемента. Используя этот подход, операция выполняется просто на простом векторе с двойным числом и очень эффективна. - person masayuki takagi; 06.01.2013