From: Steve L. <sjl...@mi...> - 2014-04-23 23:54:37
|
Hi everybody, First, let me thank you for all your hard work on SBCL! I have a question about SBCL optimizations for slot types in classes. I've noticed that when using (with-slots ...) in a certain way, I seem to get a lot of "unable to optimize" notices. Here's a toy example: (defclass A () ((m_num :type fixnum :initform 2))) (defmethod f ((a A)) (with-slots (m_num) a (incf m_num))) Upon compiling function f, I receive a number of optimization notices: ; note: unable to ; optimize ; due to type uncertainty: ; The first argument is a NUMBER, not a FLOAT. ; ; note: unable to ; associate +/+ of constants ; due to type uncertainty: ; The first argument is a NUMBER, not a RATIONAL. ; ; note: unable to ; associate +/- of constants ; due to type uncertainty: ; The first argument is a NUMBER, not a RATIONAL. ; ; note: forced to do GENERIC-+ (cost 10) ; unable to do inline fixnum arithmetic (cost 1) because: ; The first argument is a T, not a FIXNUM. ; The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES FIXNUM &REST T). ; unable to do inline fixnum arithmetic (cost 2) because: ; The first argument is a T, not a FIXNUM. ; The result is a (VALUES NUMBER &OPTIONAL), not a (VALUES FIXNUM &REST T). ; etc. I'm new to the SBCL flavor of common lisp, and may be misinterpreting the above notices. It seems to me that perhaps SBCL is not inferring that m_num is a fixnum, and hence can't optimize (inc f ...) fully. Is that correct? Perhaps not though, because adding (declare (fixnum m_num)) right before the (incf ...) doesn't seem to help. If so, any advice or pointers to improve the optimization would be greatly appreciated! Thanks so much! Best regards, Steve |
From: Christophe R. <cs...@ca...> - 2014-04-24 05:42:08
|
Steve Levine <sjl...@mi...> writes: > I have a question about SBCL optimizations for slot types in classes. I've > noticed that when using (with-slots ...) in a certain way, I seem to get a > lot of "unable to optimize" notices. Here's a toy example: > > (defclass A () > ((m_num > :type fixnum > :initform 2))) > > > (defmethod f ((a A)) > (with-slots (m_num) a > (incf m_num))) The problem with SBCL acting on the information that the slot is declared to be of type fixnum here, is that the class can be redefined at any time, including changing any and all slot constraints, *without* the method needing to be touched at all. So if we compiled the method function while baking in the fixnumness of the slot, we would have to invalidate that function on incompatible class redefinition. This isn't impossible, but it's hard. Generally, CLOS has enough layers of indirection that it's not easy to get maximum speed out of it; if your design has settled down enough that redefinition is not necessary, you could consider using structures for the things needing type inference and speed. Alternatively, (defmethod f ((a A)) (let ((m (slot-value a 'm_num))) (declare (type fixnum m) (optimize speed (safety 0))) (incf m) (setf (slot-value a 'm_num) m))) is inelegant but the fixnum addition should be fast. Cheers, Christophe |
From: Steve L. <sjl...@mi...> - 2014-04-24 15:58:12
|
Thank you Christophe! Your explanation if clear and useful, and your example works great. I've been digging a little deeper and using structures as you recommend, which has led me to a similar question. I find that when using structures, SBCL similarly does not infer type when I use (slot-value ...), but if I use the corresponding accessor for the slot, everything works great. This isn't a problem for me (as I'll just use the accessors), but I was curious why this might be. Here is another toy example, which uses a structure A and four methods x, y, z, and w (that all do the same thing differently): (defstruct A (m-num 0 :type fixnum)) ;; Optimizes great! (defmethod x ((a A)) (declare (optimize speed (safety 0))) (incf (a-m-num a))) ;; Also works great! (defmethod y ((a A)) (declare (optimize speed (safety 0))) (with-accessors ((m-num a-m-num)) a (incf m-num))) ;; Can't optimize the incf (defmethod z ((a A)) (declare (optimize speed (safety 0))) (incf (slot-value a 'm-num))) ;; Also can't optimize (because macroexpands to slot-value) (defmethod w ((a A)) (declare (optimize speed (safety 0))) (with-slots (m-num) a (incf m-num))) This example shows that using the accessor (a-m-num ...), or the similar (with-accessors ...) macro, optimizes great. However, using (slot-value ...) or the corresponding (with-slots ...) macro doesn't optimize the incf. Again, not a problem, but I'm curious. Thanks again for all your useful help! Best regards, Steve On Thu, Apr 24, 2014 at 1:41 AM, Christophe Rhodes <cs...@ca...> wrote: > Steve Levine <sjl...@mi...> writes: > > > I have a question about SBCL optimizations for slot types in classes. > I've > > noticed that when using (with-slots ...) in a certain way, I seem to get > a > > lot of "unable to optimize" notices. Here's a toy example: > > > > (defclass A () > > ((m_num > > :type fixnum > > :initform 2))) > > > > > > (defmethod f ((a A)) > > (with-slots (m_num) a > > (incf m_num))) > > The problem with SBCL acting on the information that the slot is > declared to be of type fixnum here, is that the class can be redefined > at any time, including changing any and all slot constraints, *without* > the method needing to be touched at all. So if we compiled the method > function while baking in the fixnumness of the slot, we would have to > invalidate that function on incompatible class redefinition. > > This isn't impossible, but it's hard. Generally, CLOS has enough layers > of indirection that it's not easy to get maximum speed out of it; if > your design has settled down enough that redefinition is not necessary, > you could consider using structures for the things needing type > inference and speed. Alternatively, > (defmethod f ((a A)) > (let ((m (slot-value a 'm_num))) > (declare (type fixnum m) (optimize speed (safety 0))) > (incf m) > (setf (slot-value a 'm_num) m))) > is inelegant but the fixnum addition should be fast. > > Cheers, > > Christophe > |
From: Christophe R. <cs...@ca...> - 2014-04-24 16:18:51
|
Steve Levine <sjl...@mi...> writes: > I've been digging a little deeper and using structures as you recommend, > which has led me to a similar question. I find that when using structures, > SBCL similarly does not infer type when I use (slot-value ...), but if I > use the corresponding accessor for the slot, everything works great. This > isn't a problem for me (as I'll just use the accessors), but I was curious > why this might be. Firstly, be aware that using with-slots on structures isn't portable, though SBCL allows it (possibly even documenting it somewhere). with-accessors on structures is fine. Secondly, slot-value in SBCL goes through the metaobject protocol, where again there is the possibility of redefinition -- in principle, someone could define a slot-value-using-class method applicable to a structure class which would override the standard behaviour. Although in principle this might not be allowed, and probably is not likely to happen in practice, defending against it to allow the optimization is not an awful lot of fun, and probably not of great value either. (Also, it was probably not wise of me to recommend (safety 0) to you -- that really does give you licence to shoot yourself and everyone else in the foot. Only use (safety 0) on utterly speed-critical thoroughly-debugged code, within the local contour of your application bottleneck.) Cheers, Christophe |
From: James M. L. <llm...@gm...> - 2014-04-24 18:54:59
|
On Thu, Apr 24, 2014 at 11:57 AM, Steve Levine <sjl...@mi...> wrote: > Thank you Christophe! Your explanation if clear and useful, and your example > works great. > > I've been digging a little deeper and using structures as you recommend, > which has led me to a similar question. I find that when using structures, > SBCL similarly does not infer type when I use (slot-value ...), but if I use > the corresponding accessor for the slot, everything works great. This isn't > a problem for me (as I'll just use the accessors), but I was curious why > this might be. One approach is to put off the decision between defstruct and defslots. I use an intermediate macro called defslots (http://goo.gl/imQvw1) that expands to either defstruct or defclass depending upon a flag. (defslots foo () ((x :initform 0 :type fixnum :reader foo-x) (stuff :reader foo-stuff))) (defgeneric increment (object)) (defmethod increment ((foo foo)) (with-foo-slots (x) foo (incf x))) (defun test () (let ((foo (make-foo-instance :stuff "bar"))) (increment foo) (list (foo-x foo) (foo-stuff foo)))) Thus you can have flexible redefinitions (defclass) when you need it, and performance (defstruct) when you need it. Not that classes defined by defclass are particularly slow; structs are just faster. The restricted feature set of single inheritance, simple slot access, and generic functions covers quite a bit of ground. If a class eventually needs the full functionality of defclass (multiple inheritance, etc.), the migration from defslots to defclass is easy. |