[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Best ways to convert integer to string, summary



Thanks for everybody involved. I feel now a great relief that my program 
is not making garbage while printing. If you feel your program is jamming
every now and then doing some ephemeral household in the backround, the 
problem can be those innocent-looking princ-to-string and format functions. 
This before/after story could be yours.

BEFORE
If meters change let's say 5 digits per second it means that every 200
seconds will generate 96000 bytes garbage. Monitoring the tempo, hour, min, 
sec & bar and beat position meters during a 3.5 minute song can generate
up to 100 000 bytes garbage when done with princ-to-string.

AFTER
Top printing speed and only 24 bytes garbage.

Here are the best solutions (this far) how to draw integers to a view 
without making garbage. The best way is to use a trap numtostring and print 
it with drawstring. This hint was supplied by Ulrich Becker (CIP90) <uhbecker@faui01.informatik.uni-erlangen.de>

(defmethod draw-integer-to-view ((view view) integer h v)
  (assert (integerp integer) (integer) "Arg ~a should be an integer" integer)
  (with-port (wptr view)
    (#_moveto h v)
    (with-pstrs ((string ""))
      (#_numtostring integer string)
      (#_drawstring string))))

#|

(let ((window (make-instance 'window)))
  (time
   (dotimes (i 1000)
     (draw-integer-to-view window i 100 100))))

|#

(dotimes (i 1000) (draw-integer-to-view window i 100 100)) took 3702 milliseconds (3.702 seconds) to run.
Of that, 416 milliseconds (0.416 seconds) were spent in The Cooperative Multitasking Experience.
 24 bytes of memory allocated.

If you are making many updates at oncemake all printing inside the with-port 
to avoid extra calls, and if you are sure you are printing only integers
remove the safety checking. The pascal string could probably be allocated 
only once, and numtosring all stuff into that? I did not yet test that.

The second best solution uses drawchar trap for individual digits and
was given by Karsten Poeck <poeck@informatik.uni-wuerzburg.de>. It has
the same memory efficiency characteristics as the above solution, but
going through all the digits with Lisp calls makes it more slow.

(defmethod draw-integer-to-view ((view view) integer position-x position-y)
  (assert (integerp integer) (integer) "Arg ~a should be an integer" integer)
  (with-port (wptr view)
    (#_moveto position-x position-y)
    (when (minusp integer)
      (#_drawChar #\-)
      (setq integer (- integer)))
    ;integer is now 0 or positive
    (labels ((*fast-print-number (number)
               (multiple-value-bind
                 (rest current)
                 (floor number 10)
                 (unless (zerop rest)
                   (*fast-print-number rest))
                 (#_drawChar
                  (svref  #(#\0 #\1  #\2  #\3  #\4  #\5  #\6  #\7  #\8
#\9) current ))
                 )))
      (declare (dynamic-extent #'*fast-print-number))
      (*fast-print-number integer))))

#|

(let ((window (make-instance 'window)))
  (time
   (dotimes (i 1000)
     (draw-integer-to-view window i 100 100))))

|#

(dotimes (i 1000) (draw-integer-to-view window i 100 100)) took 7853 milliseconds (7.853 seconds) to run.
Of that, 873 milliseconds (0.873 seconds) were spent in The Cooperative Multitasking Experience.
 24 bytes of memory allocated.
3 > 

What comes to conversion efficiency the best way to print high-level 
Lisp stuff is to use format with fill-pointer. This hint was given
by Donald H. Mitchell <dhm@pro-solution.com>. 

>Subject:   RE>Best way to convert integer to st
>Wow, 96 bytes per string.  Each time you execute this you're creating a new
>string; so, the obvious alternative is to reuse the string.  You could create a
>string w/ a fill-pointer and pass that to format.  (This suggestion assumes that 
>you need the string or find it convenient to have it.)
>
(defun test (n string)
  (setf (fill-pointer string) 0)
  (format string "~D" n)
  string) ; <-- note that format does not return the string, if you need it
you must return it.

(time (let ((string (make-array 4 :element-type 'character :fill-pointer 0)))
  (dotimes (i 1000) (test 123 string))))
(LET ((STRING (MAKE-ARRAY 4 :ELEMENT-TYPE 'CHARACTER :FILL-POINTER 0))) (DOTIMES
(I 1000) (TEST 123 STRING))) took 364 milliseconds (0.364 seconds) to run.
Of that, 45 milliseconds (0.045 seconds) were spent in The Cooperative
Multitasking Experience.
 48 bytes of memory allocated.

Princ-to-string should also work with fill-pointers. If you have lots to 
print you can reserve a global fill-pointer buffer and use it for all
conversions. Use moveto and drawstring traps for maximum printing speed 
and your program keeps in top condition.

Peter