From: Bruce L. Lambert
Subject: how to stop boxing a float?
Date: 
Message-ID: <s9oqt8tj98e74@corp.supernews.com>
Why does the following code box a single-float and how do I stop it from
doing so? Thanks.

(defun inner-product (l1 l2)
  (let ((sum 0.0s0))
    (declare (type single-float sum)
      (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
      (:explain :boxing))
    (mapc #'(lambda (e1 e2)
       (declare (type single-float e1 e2))
       (setf sum (+ sum (* e1 e2))))
      l1 l2)
    sum))


--
Bruce L. Lambert
········@uic.edu

From: Hidayet Tunc Simsek
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <389CC428.80478E42@EECS.Berkeley.Edu>
A thread has been going on since the last week that dealt with this.
See "Lisp history on Declaration types?".

The idea is that you can't stop boxing unless you INLINE your function
or perhaps even BLOCK-COMPILE.  You can manually box it yourself though,
which seems to improve performance by 5-6 fold (over 10M calls to the
function).

Tunc

"Bruce L. Lambert" wrote:
> 
> Why does the following code box a single-float and how do I stop it from
> doing so? Thanks.
> 
> (defun inner-product (l1 l2)
>   (let ((sum 0.0s0))
>     (declare (type single-float sum)
>       (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
>       (:explain :boxing))
>     (mapc #'(lambda (e1 e2)
>        (declare (type single-float e1 e2))
>        (setf sum (+ sum (* e1 e2))))
>       l1 l2)
>     sum))
> 
> --
> Bruce L. Lambert
> ········@uic.edu
From: Robert Monfera
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <389D9BE8.E3071DB3@fisec.com>
Try to work with declared arrays.  That should be much faster than what
Hidayet suggested (let us know if not).  Also, avoid LOOP or MAP* and
use DO, DO* or DOTIMES.

Robert

"Bruce L. Lambert" wrote:
>
> Why does the following code box a single-float and how do I stop it from
> doing so? Thanks.
>
> (defun inner-product (l1 l2)
>   (let ((sum 0.0s0))
>     (declare (type single-float sum)
>       (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
>       (:explain :boxing))
>     (mapc #'(lambda (e1 e2)
>        (declare (type single-float e1 e2))
>        (setf sum (+ sum (* e1 e2))))
>       l1 l2)
>     sum))
>
> --
> Bruce L. Lambert
> ········@uic.edu
From: Bruce L. Lambert
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <87q1i3$j8l$2@newsx.cc.uic.edu>
Robert Monfera <·······@fisec.com> wrote in message
······················@fisec.com...
>
> Try to work with declared arrays.  That should be much faster than what
> Hidayet suggested (let us know if not).  Also, avoid LOOP or MAP* and
> use DO, DO* or DOTIMES.

Robert:

Now I'm confused. I though loop was optimized such that it would produce
better, faster code than do, dotimes, etc. What's the basis for your
preference? Can you show a couple of examples?

-bruce
From: Robert Monfera
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <38A10681.F8230312@fisec.com>
"Bruce L. Lambert" wrote:

> Robert:
>
> Now I'm confused. I though loop was optimized such that it would
> produce
> better, faster code than do, dotimes, etc. What's the basis for your
> preference? Can you show a couple of examples?

You are right, in certain cases LOOP provides us with a specific,
expressive construct (like ACROSS), while DOTIMES doesn't, and
theoretically the former could end up with a slightly better
optimization (in fact it does result shorter assembly code).

I tried it on ACL:

;;;  Loop vs. Dotimes

;(defvar *v* (make-array 1000000 :element-type '(signed-byte 32)))

(defun loop-collect (vector &aux (result 0))
       (declare (optimize speed (safety 0))
                (type (simple-array (signed-byte 32) (*)) vector))
       (loop for element of-type (signed-byte 32) across vector
           do (incf (the (signed-byte 32) result) element)
             finally (return result)))

(defun loop-collect-2 (vector)
       (declare (optimize speed (safety 0))
                (type (simple-array (signed-byte 32) (*)) vector))
       (loop for element of-type (signed-byte 32) across vector
             sum element of-type (signed-byte 32))) ; yuck-where's it
                                                    ; in Hyperspec?

(defun loop-collect-3 (vector)
       (declare (optimize speed (safety 0)))
       (loop for element of-type (signed-byte 32)
              across (the (simple-array (signed-byte 32) (*)) vector)
             sum element of-type (signed-byte 32)))

(defun dotimes-collect (vector &aux (result 0))
       (declare (optimize speed (safety 0))
                (type (simple-array (signed-byte 32) (*)) vector))
       (dotimes (i (length vector) result)
         (incf (the (signed-byte 32) result) (aref vector i))))

All three versions ignore the overflow of result and are unsafe, but
that's irrelevant for this experiment.

From a declaration point of view, loop-collect-2 and loop-collect-3 look
identical, but only loop-collect-3 will run as fast as dotimes-collect -
I think it's easier, more predictable and integrated with the rest of CL
to use a declared DO than LOOP.

I am a little biased against LOOP and disprefer its run-on sentence-like
structure, including the COBOLish way types are declared, so take my
conclusions with a grain of salt.

All in all, the best performers are blazingly fast!

Robert
From: John Watton
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <87kd4c$29o$1@nnrp1.deja.com>
In article <·············@corp.supernews.com>,
  "Bruce L. Lambert" <········@uic.edu> wrote:
> Why does the following code box a single-float and how do I stop it
from
> doing so? Thanks.
>
> (defun inner-product (l1 l2)
>   (let ((sum 0.0s0))
>     (declare (type single-float sum)
>       (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
>       (:explain :boxing))
>     (mapc #'(lambda (e1 e2)
>        (declare (type single-float e1 e2))
>        (setf sum (+ sum (* e1 e2))))
>       l1 l2)
>     sum))

The following version of inner-product only has one single-float box
and that is for the returning value. Unfortunately this is usually
unavoidabe unless you box it yourself by using an array to pass back
the results. Initially this function had another single-float box when
I compiled it on my Windows version of ACL 5.0.1. This was eliminated
by moving the optimize declaration to the top of the function. Why this
needs to be done I do not know, but it is certainly the right thing to
do for this function. Note also that you should use 0.0f0 for single
floats. A 0.0s0 is a short float which is the same a single for Allegro
but not other Common Lisps. Also I changed the function to use incf but
this made no difference to boxing.

(defun inner-product (l1 l2)
  (declare (optimize (speed 3) (safety 1) (space 0) (compilation-time
0))
	   #+Allegro (:explain :boxing)) ;; :Explain keyword not ANSI
  (let ((sum 0.0f0)) ;; 0.0f0 is a single-float 0.0s0 is a short float
    (declare (single-float sum))
    (mapc #'(lambda (e1 e2)
	      (declare (single-float e1 e2))
	      (incf sum (* e1 e2)))
	  l1 l2)
    sum)) ;; single-float box is for return value - usually unavoidable

--
John Watton
Alcoa Inc.


Sent via Deja.com http://www.deja.com/
Before you buy.
From: Thomas A. Russ
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <ymizotczku1.fsf@sevak.isi.edu>
"Bruce L. Lambert" <········@uic.edu> writes:

> 
> Why does the following code box a single-float and how do I stop it from
> doing so? Thanks.
> 
> (defun inner-product (l1 l2)
>   (let ((sum 0.0s0))
>     (declare (type single-float sum)
>       (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
>       (:explain :boxing))
>     (mapc #'(lambda (e1 e2)
>        (declare (type single-float e1 e2))
>        (setf sum (+ sum (* e1 e2))))
>       l1 l2)
>     sum))

Could it be that the reason there is float boxing is that the value
returned by SETF is also returned by the anonymous LAMBDA.  I know that
MAPC doesn't do anything with the value, but the compiler may not be
that clever.

You could test that by trying something like this, which returns an
arbitrary value from the lambda instead of the float:

  (mapc #'(lambda (e1 e2)
            (declare (type single-float e1 e2))
            (setf sum (+ sum (* e1 e2)))
            t)    ; or (values)
       l1 l2)

-- 
Thomas A. Russ,  USC/Information Sciences Institute          ···@isi.edu    
From: Bruce L. Lambert
Subject: Re: how to stop boxing a float?
Date: 
Message-ID: <87q0lo$ira$2@newsx.cc.uic.edu>
My news server at work is a bit slow, but I saw from home last night that
someone (from Alcoa) posted the correct 'answer'. Here it why moving the
declaration to the top of the defun does the trick, according to Duane
Rettig, via personal email (thanks again Duane):

The OPTIMIZE declaration is specified in Ansi CL ads a "free
declaration", which in section 3.3.4 of the spec has a scope
that includes its body subforms, but none of its initialization
forms.  This is in direct contrast to the TYPE declaration you
gave to sum, which is a "bound declaration" and properly refers to
the variable at the head of the let.  However, since at initialization
time the compiler is not yet trusting declarations, the type
declaration for sum is ignored (or rather "not trusted").

The manual boxing of the float does cut down on consing, but this is what I
was looking for. It raises the more general question of where to put
declarations. Anyone care to offer some rules of thumb?

-bruce


Thomas A. Russ <···@sevak.isi.edu> wrote in message
····················@sevak.isi.edu...
> "Bruce L. Lambert" <········@uic.edu> writes:
>
> >
> > Why does the following code box a single-float and how do I stop it from
> > doing so? Thanks.
> >
> > (defun inner-product (l1 l2)
> >   (let ((sum 0.0s0))
> >     (declare (type single-float sum)
> >       (optimize (speed 3) (safety 1) (space 0) (compilation-time 0))
> >       (:explain :boxing))
> >     (mapc #'(lambda (e1 e2)
> >        (declare (type single-float e1 e2))
> >        (setf sum (+ sum (* e1 e2))))
> >       l1 l2)
> >     sum))