You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An implementation using a list can be as fast as the current implementation using multiple values if appropriate declarations (type and dynamic-extent) are put. If so, it is worth considering that future versions (0.3 or 0.4?) handle a list instead of multiple values.
I put the code of this experiment in a new branch: experiment.lisp
Body
Take the converter lchab-to-lrgb (LCHab to linear RGB) as an example, which is composed of the three converters, lchab-to-lab, lab-to-xyz, and xyz-to-lrgb. Below is the benchmark of the current implementation using multiple values.
(in-package:dufy-core)
;; Current implementation (automatically generated)
(declaim (inline lchab-to-lrgb)
(ftype (function* (valuesdouble-floatdouble-floatdouble-float&optional))
lchab-to-lrgb))
(defunlchab-to-lrgb (lstar cstarab hab &key (rgbspace +srgb+)
&aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed3) (safety1))
(typereal lstar cstarab hab))
(multiple-value-call#'xyz-to-lrgb
(multiple-value-call#'lab-to-xyz
(lchab-to-lab lstar cstarab hab)
:illuminant
illuminant)
:rgbspace
rgbspace))
;; benchmark
(defunbench-mv-version (num &optional (sample 10))
(let ((state (sb-ext:seed-random-state 1)))
(formatt"~&~F sec."
(time-median sample ;; Repeats SAMPLE times and returns the median time.
(print (loop repeat num
sum (multiple-value-call#'(lambda (x y z)
(declare (double-float x y z))
(+ x y z))
(lchab-to-lrgb (random100d0 state)
(- (random128d0 state) 256d0)
(- (random128d0 state) 256d0)))))))))
(bench-mv-version 5000000)
;; 0.969 sec.
And then the benchmark code for a list version will be as follows:
(defunbench-list-version (num &optional (sample 10))
(let ((state (sb-ext:seed-random-state 1)))
(formatt"~&~F sec."
(time-median sample
(print (loop repeat num
sum (destructuring-bind (x y z)
(list-lchab-to-lrgb (random100d0 state)
(- (random128d0 state) 256d0)
(- (random128d0 state) 256d0))
(declare (double-float x y z))
(+ x y z))))))))
The problem is how we append the converters list-lchab-to-lab, list-lab-to-xyz, and list-xyz-to-lrgb into list-lchab-to-lrgb. The simplest way will be apply and rcurry:
(The type (%list double-float double-float double-float) here is equivalent to (cons double-float (cons double-float (cons double-float))).)
Unfortunately alexandria:rcurry doesn't seem to be efficient here. (Maybe extra type declarations make it a bit faster.) The second simplest and much faster way is to use destructuring-bind (or a compiler-macro like rcurry expanded to destructuring-bind):
(defunlist-lchab-to-lrgb (lstar cstarab hab &key (rgbspace +srgb+)
&aux (illuminant (rgbspace-illuminant rgbspace)))
(declare (optimize (speed3) (safety1))
(typereal lstar cstarab hab))
(destructuring-bind (lstar astar bstar)
(list-lchab-to-lab lstar cstarab hab)
(declare (typedouble-float lstar astar bstar))
(destructuring-bind (x y z)
(list-lab-to-xyz lstar astar bstar :illuminant illuminant)
(declare (typedouble-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))))
(bench-list-version 5000000)
;; 1.219 sec.
It is, however, still slower than the reference.
By the way, the destructuring-bind of SBCL is expanded as follows:
(destructuring-bind (x y z)
(list-lab-to-xyz lstar astar bstar :illuminant illuminant)
(declare (typedouble-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
;; macroexpand =>
(let* ((#:g646
(sb-c::check-ds-list
(list-lab-to-xyz lstar astar bstar :illuminant illuminant) 33'(x y z)))
(x (pop#:g646))
(y (pop#:g646))
(z (pop#:g646)))
(declare (typedouble-float x y z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
In this case it will be effective to declare dynamic-extent and the type of the returned list. A desirable macro-expansion will be as follows:
(let* ((#:g650 (list-lab-to-xyz lstar astar bstar :illuminant illuminant))
(x (car#:g650)))
(declare (dynamic-extent#:g650)
(type (%list double-floatdouble-floatdouble-float) #:g650)
(typedouble-float x))
(let* ((#:g651 (cdr#:g650))
(y (car#:g651)))
(declare (type (%list double-floatdouble-float) #:g651)
(typedouble-float y))
(let* ((#:g652 (cdr#:g651))
(z (car#:g652)))
(declare (type (%list double-float) #:g652)
(typedouble-float z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))))
;; The following expansion is simpler and works on SBCL though it appears to be illegal.
(let* ((#:g653 (list-lab-to-xyz lstar astar bstar :illuminant illuminant))
(x (pop#:g653))
(y (pop#:g653))
(z (pop#:g653)))
(declare (dynamic-extent#:g653)
(type (%list double-floatdouble-floatdouble-float) #:g653)
(typedouble-float x)
(typedouble-float y)
(typedouble-float z))
(list-xyz-to-lrgb x y z :rgbspace rgbspace))
Such a macro (named typed-destructuring-bind) makes it faster than the previous one:
Summary
An implementation using a list can be as fast as the current implementation using multiple values if appropriate declarations (
type
anddynamic-extent
) are put. If so, it is worth considering that future versions (0.3 or 0.4?) handle a list instead of multiple values.I put the code of this experiment in a new branch: experiment.lisp
Body
Take the converter
lchab-to-lrgb
(LCHab to linear RGB) as an example, which is composed of the three converters,lchab-to-lab
,lab-to-xyz
, andxyz-to-lrgb
. Below is the benchmark of the current implementation using multiple values.And then the benchmark code for a list version will be as follows:
The problem is how we append the converters
list-lchab-to-lab
,list-lab-to-xyz
, andlist-xyz-to-lrgb
intolist-lchab-to-lrgb
. The simplest way will beapply
andrcurry
:(The type
(%list double-float double-float double-float)
here is equivalent to(cons double-float (cons double-float (cons double-float)))
.)Unfortunately
alexandria:rcurry
doesn't seem to be efficient here. (Maybe extra type declarations make it a bit faster.) The second simplest and much faster way is to usedestructuring-bind
(or a compiler-macro likercurry
expanded todestructuring-bind
):It is, however, still slower than the reference.
By the way, the
destructuring-bind
of SBCL is expanded as follows:In this case it will be effective to declare
dynamic-extent
and the type of the returned list. A desirable macro-expansion will be as follows:Such a macro (named
typed-destructuring-bind
) makes it faster than the previous one:Now we have (probably) achieved the same performance as the multiple-value version.
This issue has the same topic as #3.
The text was updated successfully, but these errors were encountered: