From: H. Tunc Simsek
Subject: [MOP] add-method question
Date: 
Message-ID: <3831044D.C6FFE407@EECS.Berkeley.Edu>
Hi,  I have a program that adds methods to SLOT-VALUE-USING-CLASS. The
problem
is that it takes too long in CMUCL.  The process is fairly fast in
CLISP.

(in-package "PCL")

(add-method (ensure-generic-function 'slot-value-using-class)
   (make-instance 'standard-method
	:specializers (list (find-class 'standard-class)
			    (find-class 'standard-object)
			    (intern-eql-specializer 'x))
	:lambda-list '(class object slot)
	:function #'(lambda (args next-methods)
			(declare (ignore args next-methods))
				'foo)))


Can somebody try this and see if they suffer the same problem as I do?


On another note,  CMUCL says that

(make-array 10 :element-type 'double-float)

is not a simple vector.  As such it doesn't allow SVREF to access it.
It prevents optimizing a heavy computation (runge-kutte 4th order
integration).
On CLISP for win98, the same array declared as DOUBLE-FLOAT doesn't
complain
to SVREF but allows any kind of element to be added to the array:

> (make-array 5 :element-type 'double-float)

#(NIL NIL NIL NIL NIL)

> (setf (svref * 0) 'a)

A

Thanks,
Tunc

From: Erik Naggum
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <3151727397715663@naggum.no>
* "H. Tunc Simsek" <······@EECS.Berkeley.Edu>
| On another note,  CMUCL says that
| 
| (make-array 10 :element-type 'double-float)
| 
| is not a simple vector.

  that's because a simple vector has element-type t by definition.  (and it
  has only one dimension, is not adjustable, has no fill-pointer, and is
  not displaced to another array, but you got that right, already.)

| As such it doesn't allow SVREF to access it.  It prevents optimizing a
| heavy computation (runge-kutte 4th order integration).

  if you declare it as the type it is, CMUCL will optimize it very well,
  and it won't even box the double-floats it extracts if they are only used
  very locally, with high optimize/speed settings.  CMUCL will tell you
  what you need to declare if you jack up the speed setting.

#:Erik
-- 
  Attention Microsoft Shoppers!  MS Monopoly Money 6.0 are now worthless.
From: H. Tunc Simsek
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <38311C56.2A7CC456@EECS.Berkeley.Edu>
Erik Naggum wrote:
> 
> * "H. Tunc Simsek" <······@EECS.Berkeley.Edu>
> | On another note,  CMUCL says that
> |
> | (make-array 10 :element-type 'double-float)
> |
> | is not a simple vector.
> 
>   that's because a simple vector has element-type t by definition.  (and it
>   has only one dimension, is not adjustable, has no fill-pointer, and is
>   not displaced to another array, but you got that right, already.)
> 
> | As such it doesn't allow SVREF to access it.  It prevents optimizing a
> | heavy computation (runge-kutte 4th order integration).
> 
>   if you declare it as the type it is, CMUCL will optimize it very well,
>   and it won't even box the double-floats it extracts if they are only used

o.k., but do we use AREF instead of SVREF? 

>   very locally, with high optimize/speed settings.  CMUCL will tell you
>   what you need to declare if you jack up the speed setting.
> 
> #:Erik
> --
>   Attention Microsoft Shoppers!  MS Monopoly Money 6.0 are now worthless.
From: Pierre R. Mai
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <87n1se7ism.fsf@orion.dent.isdn.cs.tu-berlin.de>
"H. Tunc Simsek" <······@EECS.Berkeley.Edu> writes:

> > | As such it doesn't allow SVREF to access it.  It prevents optimizing a
> > | heavy computation (runge-kutte 4th order integration).
> > 
> >   if you declare it as the type it is, CMUCL will optimize it very well,
> >   and it won't even box the double-floats it extracts if they are only used
> 
> o.k., but do we use AREF instead of SVREF? 

You use AREF, since SVREF only works on simple-vectors (by
definition), which a (vector double-float 10) isn't.  Think of SVREF
as a performance hack for compilers that can't figure out which method 
of access to use based on the type declarations present.

Here is the code that CMUCL generates on iA32 for this code:

(defun bench-array (x)
  (declare (double-float x))
  (let ((my-array (make-array 10 :element-type 'double-float)))
    (declare (type (vector double-float 10) my-array))
    (dotimes (i 10)
      (declare (type (integer 0 10) i))
      (setf (aref my-array i) x))
    my-array))

With setting (optimize (speed 3)):

     EC7:       MOV   EAX, 94                ; No-arg-parsing entry point
     ECC:       MOV   EBX, 40
     ED1:       MOV   ECX, 80
     ED6:       CALL  #x1000120
     EDB:       XOR   EAX, EAX
     EDD:       JMP   L1
     EDF: L0:   MOV   ECX, 40
     EE4:       CMP   ECX, EAX
     EE6:       JBE   L5
     EE8:       MOV   ECX, EAX
     EEA:       FSTD  [EDX+ECX*2+1]
     EEE:       FSTD  FR1
     EF0:       ADD   EAX, 4
     EF3: L1:   CMP   EAX, 40
     EF6:       JL    L0
     EF8:       MOV   ECX, [EBP-8]
     EFB:       MOV   EAX, [EBP-4]

     EFE:       ADD   ECX, 2
     F01:       MOV   ESP, EBP
     F03:       MOV   EBP, EAX
     F05:       JMP   ECX
     F07:       NOP

With (optimize (speed 3) (safety 0)):

      83:       MOV   EAX, 94                ; No-arg-parsing entry point
      88:       MOV   EBX, 40
      8D:       MOV   ECX, 80
      92:       CALL  #x1000120
      97:       XOR   EAX, EAX
      99:       JMP   L1
      9B: L0:   FSTD  [EDX+EAX*2+1]
      9F:       FSTD  FR1
      A1:       ADD   EAX, 4
      A4: L1:   CMP   EAX, 40
      A7:       JL    L0
      A9:       MOV   ECX, [EBP-8]
      AC:       MOV   EAX, [EBP-4]
      AF:       ADD   ECX, 2
      B2:       MOV   ESP, EBP
      B4:       MOV   EBP, EAX
      B6:       JMP   ECX

The code before L0 is the call to make-array (and to set up the
registers for the loop), the code after the JL after L1 is the usual
function return code.  I've elided argument-parsing code in front of
the code snippets and (in the case where safety > 0) the
error-handling code after the main function body.

Regs, Pierre.

-- 
Pierre Mai <····@acm.org>         PGP and GPG keys at your nearest Keyserver
  "One smaller motivation which, in part, stems from altruism is Microsoft-
   bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]
From: Pierre R. Mai
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <87k8ni7iim.fsf@orion.dent.isdn.cs.tu-berlin.de>
"H. Tunc Simsek" <······@EECS.Berkeley.Edu> writes:

> Hi,  I have a program that adds methods to SLOT-VALUE-USING-CLASS. The
> problem
> is that it takes too long in CMUCL.  The process is fairly fast in
> CLISP.
> 
> (in-package "PCL")
> 
> (add-method (ensure-generic-function 'slot-value-using-class)
>    (make-instance 'standard-method
> 	:specializers (list (find-class 'standard-class)
> 			    (find-class 'standard-object)
> 			    (intern-eql-specializer 'x))
> 	:lambda-list '(class object slot)
> 	:function #'(lambda (args next-methods)
> 			(declare (ignore args next-methods))
> 				'foo)))
> 
> 
> Can somebody try this and see if they suffer the same problem as I do?

Since you haven't specified what exactly your problem is, it is
difficult to say whether other people would suffer from it, too. ;)

In the above case:

a) What exactly is slow?  The process of adding the methods?  Or the
   process of accessing slots after you have added the methods?  And
   what is "slow"?  Could you give some benchmark code that gives your
   usage pattern, and indicates how things slow down?

b) Do you really need to add the methods via add-method, instead of
   defmethod?  And if so, have you made sure that the method-function
   is compiled?

c) What are you trying to achieve?  It might be helpful to get a
   little background on your problem, to see what can best be done
   about it...

Regs, Pierre.

-- 
Pierre Mai <····@acm.org>         PGP and GPG keys at your nearest Keyserver
  "One smaller motivation which, in part, stems from altruism is Microsoft-
   bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]
From: Espen Vestre
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <w6so26o9qx.fsf@wallace.nextel.no>
····@acm.org (Pierre R. Mai) writes:

> b) Do you really need to add the methods via add-method, instead of
>    defmethod?  And if so, have you made sure that the method-function
>    is compiled?

A good reason for doing this is if you want to add methods at run time
within a lisp runtime environment without a compiler.  However, I just
considered doing this myself, but got the impression that you have
to use make-method-lambda to create correct method lambdas?
-- 
  (espen)
From: Hidayet Tunc Simsek
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <3831B864.91549227@EECS.Berkeley.Edu>
Pierre R. Mai wrote:
> 
> "H. Tunc Simsek" <······@EECS.Berkeley.Edu> writes:
> 
> > Hi,  I have a program that adds methods to SLOT-VALUE-USING-CLASS. The
> > problem
> > is that it takes too long in CMUCL.  The process is fairly fast in
> > CLISP.
> >
> > (in-package "PCL")
> >
> > (add-method (ensure-generic-function 'slot-value-using-class)
> >    (make-instance 'standard-method
> >       :specializers (list (find-class 'standard-class)
> >                           (find-class 'standard-object)
> >                           (intern-eql-specializer 'x))
> >       :lambda-list '(class object slot)
> >       :function #'(lambda (args next-methods)
> >                       (declare (ignore args next-methods))
> >                               'foo)))
> >
> >
> > Can somebody try this and see if they suffer the same problem as I do?
> 
> Since you haven't specified what exactly your problem is, it is
> difficult to say whether other people would suffer from it, too. ;)
> 
> In the above case:
> 
> a) What exactly is slow?  The process of adding the methods?  Or the
>    process of accessing slots after you have added the methods?  And
>    what is "slow"?  Could you give some benchmark code that gives your
>    usage pattern, and indicates how things slow down?
> 
> b) Do you really need to add the methods via add-method, instead of
>    defmethod?  And if so, have you made sure that the method-function
>    is compiled?
> 
> c) What are you trying to achieve?  It might be helpful to get a
>    little background on your problem, to see what can best be done
>    about it...
> 
> Regs, Pierre.
> 
> --
> Pierre Mai <····@acm.org>         PGP and GPG keys at your nearest Keyserver
>   "One smaller motivation which, in part, stems from altruism is Microsoft-
>    bashing." [Microsoft memo, see http://www.opensource.org/halloween1.html]


a.) I'll be more precise.  What, in my opinion, is slow is ADD-METHOD.
Here is an example where
adding a method to SLOT-VALUE-USING-CLASS takes 3.5 seconds on my Ultra
2
Sparc.
In a single application approximately 40-50 ADD-METHODS will be executed
for a simple
application.

* (in-package :pcl)

#<The PCL package, 3220/6314 internal, 134/144 external>
* (lisp-implementation-type)

"CMU Common Lisp"
* (lisp-implementation-version)

"18b"
* (setf m (make-instance 'standard-method
             :specializers (list (find-class 'standard-class)
                                 (find-class 'standard-object)
                                 (intern-eql-specializer 'x))
            :lambda-list '(class object slot-name)
            :function #'(lambda (args next-methods)
                           (declare (ignore args next-methods))
                                'foo)))
Warning:  Declaring M special.

#<Standard-Method NIL (STANDARD-CLASS STANDARD-OBJECT (EQL X))
{701BF05}>

* (time (add-method (ensure-generic-function 'slot-value-using-class)
             m))
Compiling LAMBDA NIL:
Compiling Top-Level Form:

Evaluation took:
  3.53 seconds of real time
  2.63 seconds of user run time
  0.08 seconds of system run time
  0 page faults and
  1450240 bytes consed.
#<Standard-Method SLOT-VALUE-USING-CLASS (STANDARD-CLASS STANDARD-OBJECT
                                          (EQL X)) {701BF05}>




b.) I would like to use DEFMETHOD but that is a macro, and the functions
I
add are generated on the fly.
    They are accessors for algebraic slots (for instance), of a hybrid
system.


    e.g. (sorry if this is too long a posting)
   -----

;; An object is a FSM with flows associated to each state
;; The flows may be differential eqn's mixed with algebraic relations.
;; here are the accessors for algebraically defined slots of a class.
;; i.e. when you access Y you are really accessing the RHS of
;; the eqn. Y = F(X,U)

(defgeneric* standard-algebraic-accessor (object
                                          discrete-state
                                          algebraic-variable))
(defmethod standard-algebraic-accessor ((object standard-hybrid-object)
                                        discrete-state
                                        algebraic-variable)
  (declare (ignore discrete-state
                   algebraic-variable))
  :undefined)

(defgeneric* generate-algebraic-accessor-method (class
                                                 flow
                                                 state
                                                 state-slot
                                                 algebraic-slot))
(defmethod generate-algebraic-accessor-method ((class
standard-hybrid-class)
                                               (flow
standard-flow-definition)
                                               state
                                               state-slot
                                               algebraic-slot)
  (let ((rhs-function (flow-definition-rhsfunction flow))
        (state-slot-location (slot-definition-location state-slot)))

    (format t "[Generating accessor for algebraic slot ~a]"
algebraic-slot)
    (list

     (add-method (ensure-generic-function 'slot-value-using-class)
          (funcall #'make-instance 'standard-method
            :specializers (list (class-of class)
                                class
                                (pcl::intern-eql-specializer
algebraic-slot))
            :lambda-list '(class
                           object
                           algebraic-slot)
            :function
                       #'(lambda (args next-methods)
                           (declare (ignore next-methods))
                           (let ((obj (cadr args))
                                 (algebraic-slot (slot-definition-name
                                                  (caddr args))))

                             (funcall #'standard-algebraic-accessor
                                      obj
                                      (standard-instance-access
                                       obj
                                       state-slot-location)
                                      algebraic-slot)))))

     (add-method (ensure-generic-function 'standard-algebraic-accessor)
          (funcall #'make-instance 'standard-method
            :specializers (list class
                                (intern-eql-specializer state)
                                (intern-eql-specializer
(flow-definition-lhs flow)))
            :lambda-list '(object discrete-state algebraic-variable)
            :function
                       #'(lambda (args next-methods)
                           (declare (ignore next-methods))
                           (let ((obj (car args)))
                             (funcall rhs-function obj))))))))

;; here the RHS-FUNCTION is a closure obtained at some earlier step of
the
process.


c.) What am I trying to achieve?  Originally, we were working on a
language called SHIFT http://www-shift.eecs.berkeley.edu
    This was implemented in C.  The language is for describing dynamic
networks of hybrid automata (meaning an automata with
   flows in discrete modes).  With experience (and persuasion from Marco
Antoniotti) we've come to belive that Lisp is much
   better for our purposes than C as the underlying language:
        * Interpreter helps for new SHIFT users to test their ideas
        * a solid set of tools well documented (e.g. metering, lisp-doc,
sapaclisp, clmath, cl-http, screamer)
        * built in browsing, inspection and debugging capabilities
         * etc ...

   All that aside, I have an implementation running now, but I'd like to
optimize the integration algorithms (currently runge-kutte-4)
    so that there is less consing and quicker results.

   This work will be available soon for download but I need to polish it
up a bit and run some tests to see the performance
    (e.g. with a 1000 vehicles with 1-st order dynamics in the SmartAHS
framework).

Thanks,
Tunc
From: Tim Bradshaw
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <ey3emdqhloj.fsf@lostwithiel.tfeb.org>
* H Tunc Simsek wrote:
> Hi,  I have a program that adds methods to SLOT-VALUE-USING-CLASS. The
> problem

If I understand this, then you're adding a method for standard-class &
standard-object.  I wouldn't do that if you care about CLOS
performance, as it basically means that all slot access in the system
now has to go through the whole gory slot-value protocol.  It may be
that you can still keep reasonable performance for standard
metaclasses so long as you only define this for non-standard
ones.

But someone (Duane?) who has more experience than I of getting MOP
stuff to work reasonably well might have a better story about this.

> (make-array 10 :element-type 'double-float)

> is not a simple vector.  As such it doesn't allow SVREF to access
> it.

that's because it's not a SIMPLE-VECTOR in CMUCL.  From the Hyperspec:
`The type of a vector that is not displaced to another array, has no
fill pointer, is not expressly adjustable and is able to hold elements
of any type is a subtype of type simple-vector.'

The point is that SVREF wants to be able to know that the elements of
a vector are a certain size, so it can just index it by offsetting by
that size.  That size will probably be one word, which is right for an
array whose elements are pointers to general objects.  For a lisp
(like CMUCL or Allegro) which has a special double-float array type,
the size will probably be 2 words (assuming a word is 32 bits and a
double-float is 64 bits), so SVREF can't work.

> It prevents optimizing a heavy computation (runge-kutte 4th order
> integration).

However, AREF should compile very good code in this case, especially
if the type declaration is visible.  It does for both Allegro and
CMUCL anyway.

--tim
From: Duane Rettig
Subject: Re: [MOP] add-method question
Date: 
Message-ID: <4r9hqb09h.fsf@beta.franz.com>
Tim Bradshaw <···@tfeb.org> writes:

> * H Tunc Simsek wrote:
> > Hi,  I have a program that adds methods to SLOT-VALUE-USING-CLASS. The
> > problem
> 
> If I understand this, then you're adding a method for standard-class &
> standard-object.  I wouldn't do that if you care about CLOS
> performance, as it basically means that all slot access in the system
> now has to go through the whole gory slot-value protocol.  It may be
> that you can still keep reasonable performance for standard
> metaclasses so long as you only define this for non-standard
> ones.
> 
> But someone (Duane?) who has more experience than I of getting MOP
> stuff to work reasonably well might have a better story about this.

Unfortunately it's a long story :-)

It looks from further postings that the original poster's
issue is add-method speed specifically, rather than the
general problem of slot-value-using-class slowness.  However,
s-v-u-c is likely to show up after the first hurdle is
cleared, and it does come up quite often, so I want to
speak more to the s-v-u-c issue directly.

In order to deal with slot-value-using-class fully, I must
digress into a discussion of an unnamed concept that I will
call the "as if" doctrine.  This doctrine is that  when a
function is specified to call another function, it is not
necessary to actually go through the calling sequence via
that function's name/function object, but the functionality
that is actually executed must act "as if" that function were
actually called, as viewed externally by any program unit
other than a debugger (I would regard trace and step as
pieces of a debugger).

Sorry for the length of this post;  If you already know what
I'm talking about, you might want to skip the optional section:

<optional>

To repeat, the "as if" doctrine is the concept that when a
function is  specified to call another function, it is not
necessary to actually go through the calling sequence via
that function's name/function object, but the functionality
that is actually executed must act "as if" that function were
actually called, as viewed externally by any program unit
other than a debugger.

This doctrine is implied not only in the MOP, but also in
Common Lisp itself, and is in fact present in some
implementations of other languages like C.

The most notable surprise you might find is when in a C
program you try to set a breakpoint on a library function
such as strcmp(), and the breakpoint is never hit, though
the functionality is performed "as if" strcmp() had been
called.  This is because in some architectures some of these
low-level string operations are more efficiently done in
hardware than through a function call.  This was actually
more true in some CISC architectures (notably IBM 370)
than in more modern RISC architectures, but other library
calls might be found that do not actually call what they
were programmed to call.  In fact, on NT, a series of macros
turn general function calls such as CreateFile() into either
CreateFileA() or CreateFileW(), depending on whether Ascii
or Wide character sets are being compiled for.

The Common Lisp spec does not generally force implementations,
but there are times when the "as if" doctrine is explicitly
specified.  A search through the ansi spec for the phrase
"as if" will reveal a huge number of these, where particulars
in the functionality of one function will be defined by relating
it to another well-defined function.  This is done without ever
requiring that the former function call the latter, but only that
it act as if it had.

Another more subtle instance of the "as if" doctrine in CL is
that compiling a call to a function produces code that acts
"as if" that function were actually called.  However, inlining
and compiler-macros might cause the actual implementation to never
actually call that function.  For example, in Allegro CL, a
call such a (+ a b) will generally turn into a call to excl::+_2op
with a and b as arguments, functioning "as if" + were being actually
called.  And in fact, depending on declarations, the call to
excl::+_2op might be further transformed into machine instructions,
resulting in not even excl::+_2op being actually called.  The
requirement is that, other than the difference in visibility to
a debugger, the result of the operation be identical to the original
call (i.e. "as if" the original call had been made).

</optional>

The MOP specifies that slot-value is implemented by calling
slot-value-using-class.  However, this gf is a fairly heavyweight
implementation; it requires that the effective-slot-definition
metaobject be found and passed, and, because s-v-u-c is generic,
the class of the effective-slot-definition metaobject will
determine how the slot is to be accessed.

For standard-objects, which thus have
standard-effective-slot-definitions, this is gross overkill;
usually for these objects there is a very simple way to access
a slot, involving a quick lookup (or cacheing) of the index
based on the name, and then simply accessing the slot based
on that index.  This shortcut does not involve looking up the
actual effective-slot-definition, nor does it involve dispatching
on a slot-value-using-class effective method.  The shortcut is
used on most slot-value accesses, and is much faster than the
prescribed long method of actually going through s-v-u-c.

However, the "as if" doctrine requires that such a shortcut be
disabled if any methods are hung onto s-v-u-c, because otherwise
the effective-method would not be calculated, because the actual
generic-function call to s-v-u-c was being short-circuited.

The "closette" implementation implements a shortcut similar to
the one I described, in the AMOP book, p 282.  However, it
does not implement the "as if" doctrine, because the shortcut
function (i.e. std-slot-value) is always called for standard
objects regardless of whether there are methods attached to
s-v-u-c, and thus s-v-u-c is never called.  This is a bug
in closette, because it does not act "as if" the specification
for s-v-u-c on page 236 is being followed.

-- 
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704
Phone: (510) 548-3600; FAX: (510) 548-8253   ·····@Franz.COM (internet)