Hi. I have been always using defstruct because seemed the most natural
for structural programs. But I was seeing that most of the people now
use defclass instead. So I wanted to try classes but I was concerned
about the performance. Thefore I created 1 class and 1 structure with
an float specialized array for both and made some tests. The code for
the test class is this:
(defun check-class (times)
(with-slots (array) *clase* ;just the test object
(time
(dotimes (i times)
(dotimes (j 100) ;the dimensions of the array
(dotimes (z 100)
(setf (aref array j z) 0d0)))))))
It is just to transverse the array several times setfing the positions
with a constant. And the code for the structure is pretty the same.
For 1000 times and running on SBCL these are my results:
check-class, 1.735 sec, consing 160MB
check-structure, 0.1 sec, consing 7KB
That is, the class make it more than 17 times slower. My question is
why? and why the code of the class cons that much and how this could
be avoided?
I noticed something strange in the definition of the variables.
Because the array for the class and the structure is float specialized
when I defined 1 structured it gave me an error type if I didn't
specify in make-array the proper type with :element-type. But making
the instance of the class allowed me this. Why?
Thanks
On Mar 16, 3:03 pm, Ashrentum <·········@gmail.com> wrote:
> Hi. I have been always using defstruct because seemed the most natural
> for structural programs. But I was seeing that most of the people now
> use defclass instead. So I wanted to try classes but I was concerned
> about the performance. Thefore I created 1 class and 1 structure with
> an float specialized array for both and made some tests. The code for
> the test class is this:
>
> (defun check-class (times)
> (with-slots (array) *clase* ;just the test object
> (time
> (dotimes (i times)
> (dotimes (j 100) ;the dimensions of the array
> (dotimes (z 100)
> (setf (aref array j z) 0d0)))))))
>
> It is just to transverse the array several times setfing the positions
> with a constant. And the code for the structure is pretty the same.
> For 1000 times and running on SBCL these are my results:
>
> check-class, 1.735 sec, consing 160MB
> check-structure, 0.1 sec, consing 7KB
>
> That is, the class make it more than 17 times slower. My question is
> why? and why the code of the class cons that much and how this could
> be avoided?
>
> I noticed something strange in the definition of the variables.
> Because the array for the class and the structure is float specialized
> when I defined 1 structured it gave me an error type if I didn't
> specify in make-array the proper type with :element-type. But making
> the instance of the class allowed me this. Why?
>
> Thanks
First of all you should be explicit about which implementation you are
using. Secondly, I'd bet that your struct, class, and CHECK-STRUCT
function looks like the following:
(defstruct s array)
(defclass c () ((array :initarg :array)))
(defun check-struct (times)
(let ((array (s-array *struct*)))
(time
(dotimes (i times)
(dotimes (j 100)
(dotimes (z 100)
(setf (aref array j z) 0.0d0)))))))
In this case you are avoiding the SLOT-VALUE calls that WITH-SLOTS
sets up in lieu of ARRAY using SYMBOL-MACROLET. Try writing CHECK-
CLASS as
(defun check-class (times)
(let ((array (slot-value *clase* 'array)))
(time
(dotimes (i times)
(dotimes (j 100)
(dotimes (z 100)
(setf (aref array j z) 0.0d0)))))))
And you will probably notice a big speedup.
The morale: use WITH-SLOTS only when you really need to access a slot
"place" trough the class instance, not as a mere abbreviation. In
this last case extracting the value in a LET may be more appropriate.
Apart from that, This may be also a spot where optimization would be
welcome. Some implementations may optimize L-value and R-value uses
of the slot name from WITH-SLOTS.
Cheers
--
Marco
On Mar 16, 7:34 am, Marco Antoniotti <·······@gmail.com> wrote:
> On Mar 16, 3:03 pm, Ashrentum <·········@gmail.com> wrote:
>
>
>
>
>
> > Hi. I have been always using defstruct because seemed the most natural
> > for structural programs. But I was seeing that most of the people now
> > use defclass instead. So I wanted to try classes but I was concerned
> > about the performance. Thefore I created 1 class and 1 structure with
> > an float specialized array for both and made some tests. The code for
> > the test class is this:
>
> > (defun check-class (times)
> > (with-slots (array) *clase* ;just the test object
> > (time
> > (dotimes (i times)
> > (dotimes (j 100) ;the dimensions of the array
> > (dotimes (z 100)
> > (setf (aref array j z) 0d0)))))))
>
> > It is just to transverse the array several times setfing the positions
> > with a constant. And the code for the structure is pretty the same.
> > For 1000 times and running on SBCL these are my results:
>
> > check-class, 1.735 sec, consing 160MB
> > check-structure, 0.1 sec, consing 7KB
>
> > That is, the class make it more than 17 times slower. My question is
> > why? and why the code of the class cons that much and how this could
> > be avoided?
>
> > I noticed something strange in the definition of the variables.
> > Because the array for the class and the structure is float specialized
> > when I defined 1 structured it gave me an error type if I didn't
> > specify in make-array the proper type with :element-type. But making
> > the instance of the class allowed me this. Why?
>
> > Thanks
>
> First of all you should be explicit about which implementation you are
> using. Secondly, I'd bet that your struct, class, and CHECK-STRUCT
> function looks like the following:
>
> (defstruct s array)
>
> (defclass c () ((array :initarg :array)))
>
> (defun check-struct (times)
> (let ((array (s-array *struct*)))
> (time
> (dotimes (i times)
> (dotimes (j 100)
> (dotimes (z 100)
> (setf (aref array j z) 0.0d0)))))))
>
> In this case you are avoiding the SLOT-VALUE calls that WITH-SLOTS
> sets up in lieu of ARRAY using SYMBOL-MACROLET. Try writing CHECK-
> CLASS as
>
> (defun check-class (times)
> (let ((array (slot-value *clase* 'array)))
> (time
> (dotimes (i times)
> (dotimes (j 100)
> (dotimes (z 100)
> (setf (aref array j z) 0.0d0)))))))
>
> And you will probably notice a big speedup.
>
> The morale: use WITH-SLOTS only when you really need to access a slot
> "place" trough the class instance, not as a mere abbreviation.
A good solution is to create a different flavor of WITH-SLOTS: one
which is just a shallow binding construct that pulls out the values,
and expands to a LET rather than SYMBOL-MACROLET.(eval-when (:compile-
toplevel :load-toplevel :execute)
(defun make-slot-bindings (slot-forms instance-form)
(loop for e in slot-forms
collecting
(cond
((consp e)
(when (or (not (= (length e) 2))
(not (symbolp (first e)))
(not (symbolp (second e))))
`(,(first e) (slot-value ,instance-form ',(second
e)))))
((symbolp e)
`(,e (slot-value ,instance-form ',e)))
(t (error "with-slot-*: slot entry ~a must be a symbol."
e))))))
(defmacro with-slot-values ((&rest slot-forms) instance-form &body
forms)
"A macro similar to with-slots, except rather than creating
local symbol macro nicknames for expressions that access the slots of
the
object, it pulls out the slot values and binds them to local variables
which cache these values. This is more efficient when a slot's value
is used
more than once, but of course, these cached values become stale if the
original slots are modified."
(let* ((instance-variable (gensym))
(slot-bindings (make-slot-bindings slot-forms instance-
variable)))
`(let ((,instance-variable ,instance-form))
(let ,slot-bindings ,@forms))))
With this macro, you can simply change some instances of WITH-SLOTS to
WITH-SLOT-VALUES, without having to edit the remaining syntax.
> A good solution is to create a different flavor of WITH-SLOTS: one
> which is just a shallow binding construct that pulls out the values,
> and expands to a LET rather than SYMBOL-MACROLET.(eval-when (:compile-
BIND is a generalized binding macro (let/destructuring-bind/multiple-
value-bind/etc) and it supports things like :read-only-accessors,
etc...
http://common-lisp.net/project/metabang-bind/
- attila
thus spoke Ashrentum <·········@gmail.com>:
> (defun check-class (times)
> (with-slots (array) *clase* ;just the test object
> (time
> (dotimes (i times)
> (dotimes (j 100) ;the dimensions of the array
> (dotimes (z 100)
> (setf (aref array j z) 0d0)))))))
> It is just to transverse the array several times setfing the positions
> with a constant. And the code for the structure is pretty the same.
> For 1000 times and running on SBCL these are my results:
> check-class, 1.735 sec, consing 160MB
> check-structure, 0.1 sec, consing 7KB
> That is, the class make it more than 17 times slower. My question is
> why? and why the code of the class cons that much and how this could
> be avoided?
As for slot access, structure slot access amounts to inlined accessors,
dereferencing a vector elt pointing to the slot value. That's O(1).
For classes, SLOT-VALUE has to look up the named slot's position first,
since not only the class cannot be ascertained at compile time, but also
redefined later.
Using accessors instead of SLOT-VALUE (into which WITH-SLOTS expands)
might provide a speedup under some implementations.
--
Nawet świnka wejdzie na drzewo kiedy ją chwalą.
Ashrentum <·········@gmail.com> writes:
> Hi. I have been always using defstruct because seemed the most natural
> for structural programs. But I was seeing that most of the people now
> use defclass instead. So I wanted to try classes but I was concerned
> about the performance. Thefore I created 1 class and 1 structure with
> an float specialized array for both and made some tests. The code for
> the test class is this:
>
> (defun check-class (times)
> (with-slots (array) *clase* ;just the test object
> (time
> (dotimes (i times)
> (dotimes (j 100) ;the dimensions of the array
> (dotimes (z 100)
> (setf (aref array j z) 0d0)))))))
>
> It is just to transverse the array several times setfing the positions
> with a constant. And the code for the structure is pretty the same.
> For 1000 times and running on SBCL these are my results:
>
> check-class, 1.735 sec, consing 160MB
> check-structure, 0.1 sec, consing 7KB
>
> That is, the class make it more than 17 times slower. My question is
> why? and why the code of the class cons that much and how this could
> be avoided?
It really depends on the implementation. It can be avoided by
choosing the right implementation.
In clisp:
C/USER[8]> (defclass c ()
((array :initarg :array :accessor c-array
:initform (make-array '(100 100)
:element-type 'double-float
:initial-element 1.0d0))))
#1=#<STANDARD-CLASS C :VERSION 1>
C/USER[9]> (defstruct s
(array (make-array '(100 100)
:element-type 'double-float
:initial-element 1.0d0) ))
S
C/USER[10]> (defparameter *clase* (make-s))
*CLASE*
C/USER[14]> (dolist (*clase* (list (make-s) (make-instance 'c)))
(print (class-of *clase*))
(check-class 100))
#1=#<STRUCTURE-CLASS S>
Real time: 6.370245 sec.
Run time: 3.120195 sec.
Space: 0 Bytes
#1=#<STANDARD-CLASS C :VERSION 1>
Real time: 9.531664 sec.
Run time: 3.140196 sec.
Space: 0 Bytes
NIL
C/USER[15]> (compile 'check-class)
CHECK-CLASS ;
NIL ;
NIL
C/USER[16]> (dolist (*clase* (list (make-s) (make-instance 'c)))
(print (class-of *clase*))
(check-class 100))
#1=#<STRUCTURE-CLASS S>
Real time: 1.221652 sec.
Run time: 0.424026 sec.
Space: 0 Bytes
#1=#<STANDARD-CLASS C :VERSION 1>
Real time: 1.347462 sec.
Run time: 0.364023 sec.
Space: 0 Bytes
NIL
C/USER[17]>
Otherwise, you should probably avoid to use with-slots, unless you
really mean it. The point is that with-slots expands to
symbol-macrolets which expand to slot-value and (setf (slot-value ...)
...) all over the body, and this is most inefficient. Unless you are
expect the side effects, either in called functions, or in other
threads.
It would be better to write:
(let ((array (slot-value object 'array)))
(time
(dotimes (i times)
(dotimes (j (array-dimension array 0)) ; guess what it is...
(dotimes (z (array-dimension array 1)) ; do we really need to comment anything?
(setf (aref array j z) 0d0))))))
and obviously, then the access times will be strictly identical,
whatever the structure or object the array comes from.
--
__Pascal Bourguignon__ http://www.informatimago.com/
"Do not adjust your mind, there is a fault in reality"
-- on a wall many years ago in Oxford.
I have prepared a battery of tests for the implementations: sbcl,
clisp and allegro.
The tests. The body is the same as before for all of them, and the
only difference is the binding of the array:
1: check-struc-let-accessor
2: check-class-with-slots
3: check-class-slot-value
4: check-class-let-accessor
Below is the code of the tests.
Results:
;;SBCL 1:1.0.6
time (sec) consed
test1 0.132 8KB
test2 1.724 160MB
test3 1.418 160MB
test4 1.371 160MB
;;CLISP 1:2.41
time (sec) consed
test1 5.497 0
test2 9.647 0
test3 5.386 0
test4 5.398 0
;;Allegro CL 8.1 Free Express Edition
time (sec) consed
test1 1.790 0
test2 3.830 60 cells + 3.8KB
test3 1.790 30 cells + 3.4KB
test4 1.810 30 cells + 3.4KB
Conclusions:
- Yes, all depends on the implementation.
- Seems that it is pretty the same use structures or classes. SBCL
might be able to make many optimizations with the structure code. For
CLISP and Allegro doesn't make any difference at all. Actually the
class checks run a little bit faster for CLISP.
- As you well said me, with-slots make it slower. The difference in
SBCL is small but is very important in CLISP and Allegro
- SBCL outperforms other implementations at least for this test. But
uses too much space.
- Anyway, this is just a stupid test, and many other things should be
tested to make a real comparison between classes and structures. But
just this was very important for my current program.
I guess I could try the flexibility that offer me the classes.
Question:
Because I use mainly SBCL, I want to erase the consing. Do you know
why makes that much consing?
;;The code:
(in-package :cl-user)
(defclass c ()
((array
:initarg :array :accessor c-array :type (simple-array double-float
(* *))
:initform (make-array '(100 100) :element-type 'double-float))))
(defstruct s
(array (make-array '(100 100) :element-type 'double-float)
:type (simple-array double-float (* *))))
(defparameter *c* (make-instance 'c))
(defparameter *s* (make-s))
(defmacro check-class&struct ()
(let ((body
'(time
(dotimes (i times)
(dotimes (j (array-dimension array 0))
(dotimes (z (array-dimension array 1))
(setf (aref array j z) 0d0)))))))
`(progn
(defun check-struc-let-accessor (obj times)
(let ((array (s-array obj)))
(print "struc-let-accessor") ,body))
(defun check-class-with-slots (obj times)
(with-slots (array) obj
(print "class-with-slots") ,body))
(defun check-class-slot-value (obj times)
(let ((array (slot-value obj 'array)))
(print "class-slot-value") ,body))
(defun check-class-let-accessor (obj times)
(let ((array (c-array obj)))
(print "class-let-accessor") ,body)))))
(check-class&struct)
(defun tests (times obj-struc obj-class)
(check-struc-let-accessor obj-struc times)
(check-class-with-slots obj-class times)
(check-class-slot-value obj-class times)
(check-class-let-accessor obj-class times))
On Mar 16, 1:10 pm, Ashrentum <·········@gmail.com> wrote:
> I have prepared a battery of tests for the implementations: sbcl,
> clisp and allegro.
>
> The tests. The body is the same as before for all of them, and the
> only difference is the binding of the array:
>
> 1: check-struc-let-accessor
> 2: check-class-with-slots
> 3: check-class-slot-value
> 4: check-class-let-accessor
>
> Below is the code of the tests.
>
> Results:
>
> ;;SBCL 1:1.0.6
> time (sec) consed
> test1 0.132 8KB
> test2 1.724 160MB
> test3 1.418 160MB
> test4 1.371 160MB
>
> ;;CLISP 1:2.41
> time (sec) consed
> test1 5.497 0
> test2 9.647 0
> test3 5.386 0
> test4 5.398 0
>
> ;;Allegro CL 8.1 Free Express Edition
> time (sec) consed
> test1 1.790 0
> test2 3.830 60 cells + 3.8KB
> test3 1.790 30 cells + 3.4KB
> test4 1.810 30 cells + 3.4KB
>
> Conclusions:
> - Yes, all depends on the implementation.
> - Seems that it is pretty the same use structures or classes. SBCL
> might be able to make many optimizations with the structure code. For
> CLISP and Allegro doesn't make any difference at all. Actually the
> class checks run a little bit faster for CLISP.
> - As you well said me, with-slots make it slower. The difference in
> SBCL is small but is very important in CLISP and Allegro
> - SBCL outperforms other implementations at least for this test. But
> uses too much space.
> - Anyway, this is just a stupid test, and many other things should be
> tested to make a real comparison between classes and structures. But
> just this was very important for my current program.
>
> I guess I could try the flexibility that offer me the classes.
>
> Question:
> Because I use mainly SBCL, I want to erase the consing. Do you know
> why makes that much consing?
>
> ;;The code:
>
> (in-package :cl-user)
>
> (defclass c ()
> ((array
> :initarg :array :accessor c-array :type (simple-array double-float
> (* *))
> :initform (make-array '(100 100) :element-type 'double-float))))
>
> (defstruct s
> (array (make-array '(100 100) :element-type 'double-float)
> :type (simple-array double-float (* *))))
>
> (defparameter *c* (make-instance 'c))
> (defparameter *s* (make-s))
>
> (defmacro check-class&struct ()
> (let ((body
> '(time
> (dotimes (i times)
> (dotimes (j (array-dimension array 0))
> (dotimes (z (array-dimension array 1))
> (setf (aref array j z) 0d0)))))))
>
> `(progn
> (defun check-struc-let-accessor (obj times)
> (let ((array (s-array obj)))
> (print "struc-let-accessor") ,body))
> (defun check-class-with-slots (obj times)
> (with-slots (array) obj
> (print "class-with-slots") ,body))
> (defun check-class-slot-value (obj times)
> (let ((array (slot-value obj 'array)))
> (print "class-slot-value") ,body))
> (defun check-class-let-accessor (obj times)
> (let ((array (c-array obj)))
> (print "class-let-accessor") ,body)))))
>
> (check-class&struct)
>
> (defun tests (times obj-struc obj-class)
> (check-struc-let-accessor obj-struc times)
> (check-class-with-slots obj-class times)
> (check-class-slot-value obj-class times)
> (check-class-let-accessor obj-class times))
Try turning on (declaim (optimize (speed 3))), and then recompile your
functions - I get no consing on SBCL.
Also, you will get warnings about unable to determine the array
dimensions in the check-class-let-accessor case.
So:
(defun check-struc-let-accessor (obj times)
(declare (type fixnum times))
(let ((array (s-array obj)))
(print "struc-let-accessor")
(time
(dotimes (i times)
(dotimes (j (array-dimension array 0))
(dotimes (z (array-dimension array 1))
(setf (aref array j z) 0d0))))) ))
(defun check-class-let-accessor (obj times)
(declare (type fixnum times))
(let ((array (c-array obj)))
(declare (type (simple-array double-float (100 100)) array))
(print "class-let-accessor")
(time
(dotimes (i times)
(dotimes (j (array-dimension array 0))
(dotimes (z (array-dimension array 1))
(setf (aref array j z) 0d0)))))))
STABS> (check-struc-let-accessor *s* 1000)
"struc-let-accessor"
Evaluation took:
0.054 seconds of real time
0.052344 seconds of user run time
2.64e-4 seconds of system run time
0 calls to %EVAL
0 page faults and
0 bytes consed.
NIL
STABS> (check-class-let-accessor *c* 1000)
"class-let-accessor"
Evaluation took:
0.054 seconds of real time
0.051717 seconds of user run time
3.28e-4 seconds of system run time
0 calls to %EVAL
0 page faults and
0 bytes consed.
NIL
It appears that SBCL is able to better propagate the array type
information from the struct, and not from the class. This may be
because class slots (& presumably their declared types) can change at
any time, so you cannot trust it.
Cheers,
Brad
Ashrentum <·········@gmail.com> writes:
> Question:
> Because I use mainly SBCL, I want to erase the consing. Do you know
> why makes that much consing?
Probably from boxing/unboxing the double-floats (try to add type
declarations). But it might not matter really, since sbcl has a
generational garbage collector, and these temporary boxes are rather
efficiently collected.
--
__Pascal Bourguignon__ http://www.informatimago.com/
Nobody can fix the economy. Nobody can be trusted with their finger
on the button. Nobody's perfect. VOTE FOR NOBODY.