Conversation
num-utils.asd
Outdated
| :license "Same as NUM-UTILS -- this is part of the NUM-UTILS library." | ||
| #+asdf-unicode :encoding #+asdf-unicode :utf-8 | ||
| :depends-on (#:num-utils | ||
| :depends-on (#:num-utils/static-dispatch |
There was a problem hiding this comment.
Is this temporary? It looks like this replaces all the num-util tests with the ones from static-dispatch. Ideally we could keep two test systems until static-dispatch is ready to be folded into the main system.
There was a problem hiding this comment.
Oops, yes, my bad! I will fix it.
|
I think I can relate to tpapp now. Optimizing Common Lisp - even with SBCL - is not trivial. There are many gotchas. For instance, take (declaim (inline sequence-minimum))
(defun sequence-minimum (x)
"Return the maximum value in the sequence X"
(check-type x alexandria:proper-sequence)
(cond ((listp x) (apply 'min x))
((vectorp x) (reduce #'min x))))First of all, as of SBCL 2.3.4, (defun sequence-minimum (x)
"Return the minimum value in the sequence X"
(check-type x alexandria:proper-sequence)
(cond ((listp x)
(cond ((null x)
(error "Empty sequence"))
((null (cdr x))
(car x))
(t
(let ((min (car x)))
(dolist (elt x)
(setf min (min min elt)))
min))))
((vectorp x)
(case (length x)
(0 (error "Empty sequence"))
(1 (row-major-aref x 0))
(t (let ((min (row-major-aref x 0)))
(dotimes (index (1- (array-total-size x)))
(let ((elt (row-major-aref x (1+ index))))
(setf min (if (< elt min) elt min))))
min))))))However, even this is not enough, a (disassemble
(lambda (x)
(declare (optimize speed)
(type (simple-array single-float 1) x))
(num-utils:sequence-minimum x)))To actually avoid a generic-<, completely - and we get a 10-15x speed boost if we do this - we need something like the following: (defun sequence-minimum (x)
"Return the minimum value in the sequence X"
(check-type x alexandria:proper-sequence)
(etypecase x
(list
(cond ((null x)
(error "Empty sequence"))
((null (cdr x))
(car x))
(t
(let ((min (car x)))
(dolist (elt x)
(setf min (min min elt)))
min))))
(vector
(case (length x)
(0 (error "Empty sequence"))
(1 (row-major-aref x 0))
(t (let ((min (row-major-aref x 0)))
(declare (type (simple-array single-float 1) x)
(type single-float min)) ; <== THIS TYPE DECLARATION
(dotimes (index (1- (array-total-size x)))
(let ((elt (row-major-aref x (1+ index))))
(setf min (if (< elt min) elt min))))
min))))))But that makes the code non-generic to vector element types :/. In this particular case, polymorphic-functions too are only helpful if we write a polymorph-compiler-macro, which actually should not be necessary at all. To actually the generic versions we need better type inference. So, I'm unsure how much benefit static-dispatch alone could get us. May be a 1.5-2x, but so long as generic-+, generic-< etc remain, we are losing out on a 10x performance boost. |
|
@digikar99 I'm so sorry for the delay in this. Life gets in the way sometimes. What is your current opinion on static dispatch for these operations? I recently completed some additional work for vector operations (basically completed coverage of all operators) and have started looking at how to optimise them. |
|
No worries! I have also went down a few rabbit holes in the meanwhile. I see two ways forward.
Illustration with peltadot: (defpackage :peltadot-user
(:use :peltadot)
(:local-nicknames (:traits :peltadot-traits-library)))
(in-package :peltadot-user)
(defpolymorph sequence-minimum ((s sequence)) t
(case (traits:len s)
(0 (error "Empty sequence"))
(1 (traits:seq-ref s 0))
(t (let ((min (traits:seq-ref s 0)))
(dotimes (index (1- (traits:len s)))
(setf min (min min (traits:seq-ref s index))))
min))))
(defun type-parameter-p (s) (member s '(<t>)))
(pushnew 'type-parameter-p peltadot:*parametric-type-symbol-predicates*)
(defpolymorph sequence-minimum ((s (simple-array <t> 1))) <t>
(case (traits:len s)
(0 (error "Empty sequence"))
(1 (traits:seq-ref s 0))
(t (pflet ((min (traits:seq-ref s 0))
(elt (traits:seq-ref s 0)))
(declare (type <t> min elt))
(dotimes (index (1- (traits:len s)))
(setf elt (traits:seq-ref s index))
(setf min (min min elt)))
min))))
(defun list-min (list)
(declare (type list list)
(optimize speed))
(sequence-minimum list))
(defun array-sf-min (x)
(declare (type (simple-array single-float 1) x)
(optimize speed))
(sequence-minimum x))
(defun array-df-min (x)
(declare (type (simple-array double-float 1) x)
(optimize speed))
(sequence-minimum x)) |
|
Thanks. I'm more familiar with colatron than peltadot.
I'd love your opinion on a similar question of changing
fundamental behaviours, one that I think was touched in during a reddit
discussion. That of modifying the implementation. It seems to be that the
right way to handle this is by updating the way the implementation
dispatches. There's a few places in lisp stat where I feel papering over
CL's deficiencies is always a kludge. So far I've been loath to do that
with a moving upstream (SBCL) and the unlikelihood of these sorts of
changes making it upstream (but you dispatch stuff maybe?). I guess the
real question here is: forgoing multi-implementation for a 'better'
solution. I personally wonder if the effort placed in maintaining broad
implementation support is wise in such a small community.
…On Thu, Mar 5, 2026 at 7:00 AM Shubhamkar Ayare ***@***.***> wrote:
*digikar99* left a comment (Lisp-Stat/numerical-utilities#13)
<#13 (comment)>
No worries!
I have also went down a few rabbit holes in the meanwhile.
I see two ways forward.
1.
Use coalton <https://github.com/coalton-lang/coalton>. It has inlining
now. Particularly, it has a better notion of function types, it has
typeclasses (which I understand are similar to interfaces). There is an
overhead of learning the ML type system basics, but that's once a lifetime
thing. The other friction might be lisp-coalton interop, which exists, but
I find it less than ideal. I'd guess coalton is also a fair bit tested and
definitely more actively developed than my second suggestion below. One
downside is it can be tricky to express (i) array dimensions (ii)
simple-array is a subtype of array.
2.
Use peltadot <https://gitlab.com/digikar/peltadot> which I hacked
together over the past year or two from the polymorphic-functions and
extensible-compound-types I was working on previously. This reimplements
lisp type system, enables type-based dispatch, provides
compiler-macro-expansion time type propagation, optional static and default
dynamic dispatch, and also has traits (which are again similar to
interfaces and typeclasses). I find the integration with standard CL to be
neat. The downside is it is restricted to standard CL function types, which
are less powerful than what coalton can express. The upside, it is possible
to express simple-array as being a subtype of array.
Illustration with peltadot:
(defpackage :peltadot-user
(:use :peltadot)
(:local-nicknames (:traits :peltadot-traits-library)))
(in-package :peltadot-user)
(defpolymorph sequence-minimum ((s sequence)) t
(case (traits:len s)
(0 (error "Empty sequence"))
(1 (traits:seq-ref s 0))
(t (let ((min (traits:seq-ref s 0)))
(dotimes (index (1- (traits:len s)))
(setf min (min min (traits:seq-ref s index))))
min))))
(defun type-parameter-p (s) (member s '(<t>)))
(pushnew 'type-parameter-p peltadot:*parametric-type-symbol-predicates*)
(defpolymorph sequence-minimum ((s (simple-array <t> 1))) <t>
(case (traits:len s)
(0 (error "Empty sequence"))
(1 (traits:seq-ref s 0))
(t (pflet ((min (traits:seq-ref s 0))
(elt (traits:seq-ref s 0)))
(declare (type <t> min elt))
(dotimes (index (1- (traits:len s)))
(setf elt (traits:seq-ref s index))
(setf min (min min elt)))
min))))
(defun list-min (list)
(declare (type list list)
(optimize speed))
(sequence-minimum list))
(defun array-sf-min (x)
(declare (type (simple-array single-float 1) x)
(optimize speed))
(sequence-minimum x))
(defun array-df-min (x)
(declare (type (simple-array double-float 1) x)
(optimize speed))
(sequence-minimum x))
—
Reply to this email directly, view it on GitHub
<#13 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB3KJ4EGKV7PJQ6YVDHHG6L4PCYQJAVCNFSM6AAAAACWGFB3BCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DAMBQHAZTSOBXGU>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
|
Yes, I think we had some reddit discussions about this. Forgoing multi implementation support and focusing solely on, say SBCL, eliminates the need for compiler-macro-expansion time type propagation and dispatch. The alternative (that I'm aware of) is writing deftransforms, lots of thems. However, after looking at the above sequence-minimum example, I realized there were two other problems: function types and typeclasses. Coalton handles them well. Although it presents other problems mentioned above. |
This is still of limited use though.
In the upcoming commits, I will attempt the second task. For example, the following shows a generic-+ in its disassembly.
To get rid of it requires one to inline the
refinsidemapping-array. But after doing both of these, we get a 2.5x performance boost on SBCL 2.3.4:This can be optimized further if we allow for an optional
outargument to thee2+function.