Re: memory usage issue for double-float operations

Matt Kaufmann <[email protected]> Wed, 16 Oct 2024 10:54:20 -0500
Newsgroups gmane.lisp.steel-bank.general
Message-ID <[email protected]>
Hi,

I appreciate being able to avoid memory usage when passing
double-floats through function calls.  But I'm still running into an
issue with that, even with fixes to recent SBCL bugs 2084209 and
2084398.  Using SBCL downloaded and built this morning on my Mac (SBCL
2.4.9.52-28385348a), the simple script below results in memory usage
that surprises me, and shows that this usage disappears with inlining:

* (load "sbcl-script.lsp")
Bytes used for 10000 iterations of (foo *my-ar*): 163840
Bytes used for 10000 iterations of (foo-inline *my-ar*): 0
T
* 

Am I doing something wrong?

Thanks,
Matt [script is below]
(defvar *my-ar*
  (make-array 3
              :element-type 'double-float
              :initial-contents '(3.0d0 4.0d0 5.0d0)))

(declaim (ftype (function (t)
                          double-float) ; or (values double-float)
                aref0))

(declaim (inline aref0-inline))

(defun aref0 (ar)
  (the double-float
       (aref (the (simple-array double-float (*))
                  ar)
             0)))

(defun aref0-inline (ar)
  (the double-float
       (aref (the (simple-array double-float (*))
                  ar)
             0)))

(defun foo (ar)
  (= (the double-float
          (* 2.0d0
             (the double-float
                  (aref0 ar))))
     5.0d0))

(defun foo-inline (ar)
  (= (the double-float
          (* 2.0d0
             (the double-float
                  (aref0-inline ar))))
     5.0d0))

; Bytes used = 163840
(let ((start (sb-ext:get-bytes-consed)))
  (loop for i from 1 to 10000 do (foo *my-ar*))
  (format t "Bytes used for 10000 iterations of (foo *my-ar*): ~s~%"
          (- (sb-ext:get-bytes-consed) start)))

; Bytes used = 0
(let ((start (sb-ext:get-bytes-consed)))
  (loop for i from 1 to 10000 do (foo-inline *my-ar*))
  (format t "Bytes used for 10000 iterations of (foo-inline *my-ar*): ~s"
          (- (sb-ext:get-bytes-consed) start)))

Stas Boukarev <[email protected]> writes:

> [1:text/plain Hide]
>
> It could be made to work without any intervention at all, but it's probably
> not a good idea to automatically generate extra entry points when not
> needed.
> ftype seems reasonable and standard, for now.
>
> On Tue, Oct 8, 2024 at 6:19 PM Michał Herda | Keepit <[email protected]> wrote:
>
>> Thanks for that optimization. One more question from me: is this going to
>> work automatically with *DERIVE-FUNCTION-TYPES*, or are manual ftype
>> proclamations still necessary in that case?
>> ------------------------------
>> *From:* Stas Boukarev <[email protected]>
>> *Sent:* Tuesday, October 8, 2024 5:17 PM
>> *To:* Matt Kaufmann <[email protected]>
>> *Cc:* [email protected] <[email protected]>;
>> [email protected] <[email protected]>
>> *Subject:* Re: [Sbcl-help] memory usage issue for double-float operations
>>
>> No block compilation is needed, or any other non-standard feature.
>>
>> On Tue, Oct 8, 2024 at 6:15 PM Matt Kaufmann <[email protected]>
>> wrote:
>>
>> Thank you so much; very cool!  Is block compilation necessary for this
>> to work?
>>
>> Stas Boukarev <[email protected]> writes:
>>
>> > [1:text/plain Hide]
>> >
>> > Now unboxed returns also work. Trace works as well. The following no
>> longer
>> > conses:
>> >
>> > (declaim (type (simple-array double-float (1)) *ar*))
>> >
>> > (defvar *ar* (make-array 1
>> >                          :element-type 'double-float
>> >                          :initial-element 0.0d0))
>> >
>> > (declaim (ftype (function (double-float) (values double-float &optional))
>> >                 f1))
>> >
>> > (defun f1 (v)
>> >   (+ v v))
>> >
>> > (declaim (ftype (function (double-float) *) f2))
>> >
>> > (defun f2 (v)
>> >   (loop for i from 1 to 1000
>> >         do
>> >         (setf (aref *ar* 0) (f1 v))))
>> >
>> >
>> > On Tue, Oct 8, 2024 at 12:17 AM Stas Boukarev <[email protected]>
>> wrote:
>> >
>> >> I just pushed the initial attempt for unboxed arguments, so the
>> following
>> >> doesn't cons anymore:
>> >>
>> >> (declaim (ftype (function (double-float double-float) (values t
>> >> &optional)) f))
>> >>
>> >> (defun f (a b)
>> >>   (> (+ a b) 3d0))
>> >>
>> >> (defun bar (n)
>> >>   (declare (fixnum n))
>> >>   (f (* pi n) (* pi (1+ n))))
>> >>
>> >>
>> >> Not consing the return value will come in the near future. #'f works as
>> >> expected, and incompatible redefinitions behave reasonably. trace
>> doesn't
>> >> yet work.
>> >>
>> >> On Sun, Sep 29, 2024 at 1:46 AM Stas Boukarev <[email protected]>
>> wrote:
>> >>
>> >>> It could be improved in the future, but currently it's really not ready
>> >>> for public use and may change at any time.
>> >>> You might try block compilation instead.
>> >>>
>> >>> On Sun, Sep 29, 2024 at 1:37 AM Matt Kaufmann <[email protected]>
>> >>> wrote:
>> >>>
>> >>>> I very much appreciate all the replies, and I think I understand.
>> >>>> Thank you, all who replied!
>> >>>>
>> >>>> > > That's not directly supported by SBCL's current calling
>> conventions.
>> >>>> >
>> >>>> > It is actually supported, but we don't advertise it or make it
>> >>>> well-behaved
>> >>>> > when misused.
>> >>>>
>> >>>> In our application we can arrange for every function to have a
>> >>>> suitable ftype proclaimed before it is defined, so that double-floats
>> >>>> are passed and returned only as indicated by the function's ftype;
>> >>>> also, every function is defined before it is called.  If these suffice
>> >>>> to guarantee no "misuse" of support for avoiding boxing, and if there
>> >>>> is a reasonably simple way to provide that support (like setting some
>> >>>> global), then I'd like to know how that can work, provided that such
>> >>>> support is likely to remain available in SBCL.
>> >>>>
>> >>>> Thanks,
>> >>>> Matt
>> >>>> Stas Boukarev <[email protected]> writes:
>> >>>>
>> >>>> > [1:multipart/alternative Hide]
>> >>>> >
>> >>>> >
>> >>>> > [1/1:text/plain Hide]
>> >>>> >
>> >>>> >> That's not directly supported by SBCL's current calling
>> conventions.
>> >>>> >
>> >>>> > It is actually supported, but we don't advertise it or make it
>> >>>> well-behaved
>> >>>> > when misused.
>> >>>> >
>> >>>> > On Sun, Sep 29, 2024 at 12:30 AM Christophe Rhodes <
>> [email protected]>
>> >>>> wrote:
>> >>>> >
>> >>>> >> Matt Kaufmann <[email protected]> writes:
>> >>>> >>
>> >>>> >> > I'd really like to avoid boxing of double-floats returned by
>> >>>> function
>> >>>> >> > calls.
>> >>>> >>
>> >>>> >> Avoiding boxing is, essentially, arranging that there be a known
>> >>>> >> location for the callee and the caller to cooperate in writing and
>> >>>> >> reading an unboxed return value, without risking that value being
>> >>>> >> accidentally interpreted as a tagged lisp object pointer.
>> >>>> >>
>> >>>> >> That's not directly supported by SBCL's current calling
>> conventions.
>> >>>> >> But you can do it yourself: have your function, instead of
>> returning a
>> >>>> >> double-float, take as an argument a (simple-array double-float
>> (1)),
>> >>>> and
>> >>>> >> write its result there; the caller can read the value from there,
>> and
>> >>>> >> all should happen without consing:
>> >>>> >>
>> >>>> >> (defun f1 (v result)
>> >>>> >>   (declare (type double-float v)
>> >>>> >>            (type (simple-array double-float (1)) result))
>> >>>> >>   (setf (aref result 0) (+ v v))
>> >>>> >>   (values))
>> >>>> >>
>> >>>> >> (defvar *ar* (make-array 1 :element-type 'double-float))
>> >>>> >> (declaim (type (simple-array double-float (1)) *ar*))
>> >>>> >>
>> >>>> >> (defun f2 (v)
>> >>>> >>   (let ((f1r (make-array 1 :element-type 'double-float)))
>> >>>> >>     (declare (dynamic-extent f1r))
>> >>>> >>     (loop repeat 1000
>> >>>> >>           do (setf (aref *ar* 0) (progn (f1 v f1r) (aref f1r
>> 0))))))
>> >>>> >>
>> >>>> >> Christophe
>> >>>> >>
>> >>>> >> > Below is a small example that boxes unless the function is
>> >>>> >> > made inline.  Is there, or could there be, a way to avoid boxing
>> the
>> >>>> >> > double-float result, other than inlining?  Or should I just
>> accept
>> >>>> >> > that this is somehow not possible?  (Thanks again, Michał "phoe"
>> >>>> >> > Herda, for the reply; note that I've done all I could below to
>> avoid
>> >>>> >> > boxing inputs.)
>> >>>> >> >
>> >>>> >> > (declaim (optimize (compilation-speed 0) (debug 0) (speed 3)
>> (space
>> >>>> 0)
>> >>>> >> >                    (safety 0)))
>> >>>> >> >
>> >>>> >> > (declaim (type (simple-array double-float (1)) *ar*))
>> >>>> >> >
>> >>>> >> > (defvar *ar* (make-array '(1)
>> >>>> >> >                          :element-type 'double-float
>> >>>> >> >                          :initial-element 0.0d0))
>> >>>> >> >
>> >>>> >> > (declaim (ftype (function (double-float) double-float)
>> >>>> >> >                 f1))
>> >>>> >> >
>> >>>> >> > ; (declaim (inline f1)) ; eliminates consing for (time (f2
>> 1.0d0))
>> >>>> below
>> >>>> >> >
>> >>>> >> > (defun f1 (v)
>> >>>> >> >   (declare (type double-float v))
>> >>>> >> >   (the double-float (+ v v)))
>> >>>> >> >
>> >>>> >> > (declaim (ftype (function (double-float) *)
>> >>>> >> >                 f2))
>> >>>> >> >
>> >>>> >> > (defun f2 (v)
>> >>>> >> >   (declare (type double-float v))
>> >>>> >> >   (loop for i from 1 to 1000
>> >>>> >> >         do
>> >>>> >> >         (setf (aref (the (simple-array double-float (1))
>> >>>> >> >                          *ar*)
>> >>>> >> >                     0)
>> >>>> >> >               (the double-float (f1 v)))))
>> >>>> >> >
>> >>>> >> > ; 32,768 bytes consed, except 0 if f1 calls are inlined:
>> >>>> >> > (time (f2 1.0d0))
>> >>>> >> >
>> >>>> >> > Matt Kaufmann <[email protected]> writes:
>> >>>> >> >
>> >>>> >> >> Very interesting.  I expected the declare form in the defun of
>> >>>> >> >> my-update --
>> >>>> >> >>
>> >>>> >> >>     (declare (type (simple-array double-float (*)) ar)
>> >>>> >> >>              (type double-float v))
>> >>>> >> >>
>> >>>> >> >> -- to avoid boxing.  As another experiment I added the following
>> >>>> just
>> >>>> >> >> before the defun of my-update -- but consing still occurred.
>> >>>> >> >>
>> >>>> >> >> (declaim (ftype (function ((simple-array double-float (*)) t
>> >>>> >> double-float)
>> >>>> >> >>                           double-float)
>> >>>> >> >>                 my-update))
>> >>>> >> >>
>> >>>> >> >> Apparently those extra efforts don't change your point about
>> >>>> boxing,
>> >>>> >> >> which contradicts what I thought I understood.  I appreciate the
>> >>>> help!
>> >>>> >> >>
>> >>>> >> >> Thanks,
>> >>>> >> >> Matt
>> >>>> >> >> Michał "phoe" Herda <[email protected]> writes:
>> >>>> >> >>
>> >>>> >> >>> Converting a double float to a function argument requires
>> boxing,
>> >>>> which
>> >>>> >> >>> allocates memory. If the function is inlined, there are no
>> >>>> arguments to
>> >>>> >> >>> pass, so there can be no consing.
>> >>>> >> >>>
>> >>>> >> >>> On 26.09.2024 01:55, Matt Kaufmann wrote:
>> >>>> >> >>>> Thanks so much!
>> >>>> >> >>>>
>> >>>> >> >>>> I see that suitable THE declarations also avoid consing.  But
>> >>>> >> >>>> inserting a function call can cause consing, whether using THE
>> >>>> or your
>> >>>> >> >>>> declaim form, as illustrated below.  I'd be interested in any
>> >>>> insights
>> >>>> >> >>>> about why that function call causes consing.
>> >>>> >> >>>>
>> >>>> >> >>>> (declaim (optimize (compilation-speed 0) (debug 0) (speed 3)
>> >>>> (space 0)
>> >>>> >> >>>>                     (safety 0)))
>> >>>> >> >>>>
>> >>>> >> >>>> ; Optional:
>> >>>> >> >>>> ; (declaim (type (simple-array double-float (5000)) *ar*))
>> >>>> >> >>>>
>> >>>> >> >>>> (defvar *ar* (make-array '(5000)
>> >>>> >> >>>>                           :element-type 'double-float
>> >>>> >> >>>>                           :initial-element 0.0d0))
>> >>>> >> >>>>
>> >>>> >> >>>> (loop with i of-type (integer 0 *) = 0 do
>> >>>> >> >>>>        (cond ((>= i 5000)
>> >>>> >> >>>>               (return))
>> >>>> >> >>>>              (t (progn (setf (aref *ar* i)
>> >>>> >> >>>>                              (float i 0.0d0))
>> >>>> >> >>>>                        (setq i (1+ i))))))
>> >>>> >> >>>>
>> >>>> >> >>>> (defun foo (ar)
>> >>>> >> >>>>    (loop with i = 0 do
>> >>>> >> >>>>          (cond
>> >>>> >> >>>>           ((>= i 4999) ; <-- for i from 0 to 4999 ...
>> >>>> >> >>>>            (return))
>> >>>> >> >>>>           (t (setf (aref (the (simple-array double-float (*))
>> ar)
>> >>>> >> >>>>                          i)
>> >>>> >> >>>>                    (the double-float
>> >>>> >> >>>>                         (+ 1.0d0
>> >>>> >> >>>>                            (the double-float
>> >>>> >> >>>>                                 (aref (the (simple-array
>> >>>> double-float
>> >>>> >> *) ar)
>> >>>> >> >>>>                                       i)))))
>> >>>> >> >>>>              (setq i (1+ i))))))
>> >>>> >> >>>>
>> >>>> >> >>>> (time (foo *ar*)) ; 0 bytes consed
>> >>>> >> >>>>
>> >>>> >> >>>> ; Optional:
>> >>>> >> >>>> (declaim (inline my-update))
>> >>>> >> >>>>
>> >>>> >> >>>> (defun my-update (ar i v)
>> >>>> >> >>>>    (declare (type (simple-array double-float (*)) ar)
>> >>>> >> >>>>             (type double-float v))
>> >>>> >> >>>>    (setf (aref (the (simple-array double-float (*)) ar)
>> >>>> >> >>>>                i)
>> >>>> >> >>>>          v))
>> >>>> >> >>>>
>> >>>> >> >>>> (defun foo2 (ar)
>> >>>> >> >>>>    (loop with i = 0 do
>> >>>> >> >>>>          (cond
>> >>>> >> >>>>           ((>= i 4999) ; <-- for i from 0 to 4999 ...
>> >>>> >> >>>>            (return))
>> >>>> >> >>>>           (t (my-update ar
>> >>>> >> >>>>                         i
>> >>>> >> >>>>                         (the double-float
>> >>>> >> >>>>                              (+ 1.0d0
>> >>>> >> >>>>                                 (the double-float
>> >>>> >> >>>>                                      (aref (the (simple-array
>> >>>> >> double-float *)
>> >>>> >> >>>>                                                 ar)
>> >>>> >> >>>>                                            i)))))
>> >>>> >> >>>>              (setq i (1+ i))))))
>> >>>> >> >>>>
>> >>>> >> >>>> (time (foo2 *ar*)) ; 65,536 bytes consed if not inlined, 0
>> >>>> otherwise
>> >>>> >> >>>>
>> >>>> >> >>>> Thanks,
>> >>>> >> >>>> Matt
>> >>>> >> >>>> Michał "phoe" Herda <[email protected]> writes:
>> >>>> >> >>>>
>> >>>> >> >>>>> (declaim (type (simple-array double-float (5000)) *ar*))
>> >>>> >> >>>>>
>> >>>> >> >>>>> On 25.09.2024 21:44, Matt Kaufmann wrote:
>> >>>> >> >>>>>> Hi,
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> Is there a way to modify the example below so that the final
>> >>>> form
>> >>>> >> does
>> >>>> >> >>>>>> not allocate memory in SBCL?  (To be fair, I tried several
>> >>>> other
>> >>>> >> Lisps
>> >>>> >> >>>>>> as well, and all of them allocated memory.)
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> (declaim (optimize (compilation-speed 0) (debug 0) (speed 3)
>> >>>> (space
>> >>>> >> 0)
>> >>>> >> >>>>>>                      (safety 0)))
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> (defvar *ar* (make-array '(5000)
>> >>>> >> >>>>>>                            :element-type 'double-float
>> >>>> >> >>>>>>                            :initial-element 0.0d0))
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> (loop with i of-type (integer 0 *) = 0 do
>> >>>> >> >>>>>>         (cond ((>= i 5000)
>> >>>> >> >>>>>>                (return))
>> >>>> >> >>>>>>               (t (progn (setf (aref *ar* i)
>> >>>> >> >>>>>>                               (float i 0.0d0))
>> >>>> >> >>>>>>                         (setq i (1+ i))))))
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> (defun foo ()
>> >>>> >> >>>>>>     (loop with i of-type fixnum = 0 do
>> >>>> >> >>>>>>           (cond
>> >>>> >> >>>>>>            ((>= i 4999) ; <-- for i from 0 to 4999 ...
>> >>>> >> >>>>>>             (return))
>> >>>> >> >>>>>>            (t (setf (aref *ar* i)
>> >>>> >> >>>>>>                     (the double-float
>> >>>> >> >>>>>>                          (+ 1.0d0
>> >>>> >> >>>>>>                             (the double-float (aref *ar*
>> i)))))
>> >>>> >> >>>>>>               (setq i (the fixnum (1+ i)))))))
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> (time (foo))
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> Thanks,
>> >>>> >> >>>>>> Matt
>> >>>> >> >>>>>>
>> >>>> >> >>>>>>
>> >>>> >> >>>>>> _______________________________________________
>> >>>> >> >>>>>> Sbcl-help mailing list
>> >>>> >> >>>>>> [email protected]
>> >>>> >> >>>>>> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>> >> >>
>> >>>> >> >>
>> >>>> >> >> _______________________________________________
>> >>>> >> >> Sbcl-help mailing list
>> >>>> >> >> [email protected]
>> >>>> >> >> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>> >> >
>> >>>> >> >
>> >>>> >> > _______________________________________________
>> >>>> >> > Sbcl-help mailing list
>> >>>> >> > [email protected]
>> >>>> >> > https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>> >>
>> >>>> >>
>> >>>> >> _______________________________________________
>> >>>> >> Sbcl-help mailing list
>> >>>> >> [email protected]
>> >>>> >> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>> >>
>> >>>> >
>> >>>> > [1/2:text/html Show Save:noname (38kB)]
>> >>>> >
>> >>>> >
>> >>>> > [2:text/plain Hide]
>> >>>> >
>> >>>> >
>> >>>> > [3:text/plain Hide]
>> >>>> >
>> >>>> > _______________________________________________
>> >>>> > Sbcl-help mailing list
>> >>>> > [email protected]
>> >>>> > https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>>
>> >>>>
>> >>>> _______________________________________________
>> >>>> Sbcl-help mailing list
>> >>>> [email protected]
>> >>>> https://lists.sourceforge.net/lists/listinfo/sbcl-help
>> >>>>
>> >>>
>> >
>> > [2:text/html Show Save:noname (54kB)]
>>
>>
>>
>>
>> This e-mail is sent to you from Keepit A/S. Dedicated SaaS Data Protection.
>> VAT: DK30806883, Per Henrik Lings Allé 4, 7., DK-2100 Copenhagen Ø,
>> Denmark.
>>
>> This e-mail is sent to you directly and is meant for nobody else. If the
>> e-mail contains personal data that Keepit is responsible for and the e-mail
>> was not meant for you, please do not forward, distribute, or copy, i.e. but
>> return the e-mail to sender. Also, do not send the e-mail to a third party
>> without making sure that you have our prior consent. Distribution of this
>> e-mail to unauthorized receivers may have legal consequences.
>> If you have received an e-mail from us and you don’t know why, then please
>> refer to our Privacy Policy <https://www.keepit.com/privacy-policy/>.
>> If you do not want to receive more e-mails from us, please contact
>> [email protected].
>>
>
> [2:text/html Show Save:noname (75kB)]


_______________________________________________
Sbcl-help mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/sbcl-help