(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
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.
Erik Naggum wrote:
>
> * "H. Tunc Simsek" <sim...@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?
> > | 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 <pm...@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]
> 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...
> 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)
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
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
> * 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 du...@Franz.COM (internet)
This seems to be very slow indeed. Running the same code from the
listener on CMU CL 18a+ 2.4.15 (Debian Version) on Linux on an
AMD-K6-2(350 MHz) I get:
* (in-package :PCL)
#<The PCL package, 2458/4069 internal, 134/167 external>
* (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)) {4815FB4D}>
* (time (add-method (ensure-generic-function 'slot-value-using-class)
m))
Compiling LAMBDA NIL:
Compiling Top-Level Form:
Evaluation took:
0.12 seconds of real time
0.12 seconds of user run time
0.0 seconds of system run time
64 page faults and
163280 bytes consed.
#<Standard-Method SLOT-VALUE-USING-CLASS (STANDARD-CLASS STANDARD-OBJECT
(EQL X)) {4815FB4D}>
This seems much more reasonable IMHO. Maybe you have run across a bug
in CMU CL 18b or the Sparc port? I'd suggest you post your numbers to
the CMU CL implementation list, the address of which can be found at
http://www.cons.org/cmucl/. Maybe others on there will be able to
find the cause of your problems.
> b.) I would like to use DEFMETHOD but that is a macro, and the
> functions I add are generated on the fly.
In this case the use of add-method might be the correct course of
action to take, but be sure to compile the functions that you add, for
maximum performance, if the methods you add are called more often than
they are added (which is usually the case).
BTW in the code you included in your message, you don't need to worry
about compilation, since the functions you add are _not_ *generated* at
run-time, but only added at run-time, which means that the functions
will have been compiled already by the file-compiler.
There are a couple of calls of the form (funcall #'bla-bla ...) the
motivation of which I don't understand, since (bla-bla ...) could be
used equally well in those places, if I'm not missing something else.
> 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).
Sounds great. Good luck with your project and your problem!
> o.k., but do we use AREF instead of SVREF?
yes.
--tim
> Evaluation took:
> 0.12 seconds of real time
> 0.12 seconds of user run time
> 0.0 seconds of system run time
> 64 page faults and
> 163280 bytes consed.
> #<Standard-Method SLOT-VALUE-USING-CLASS (STANDARD-CLASS STANDARD-OBJECT
> (EQL X)) {4815FB4D}>
there is also a noticeable difference in bytes consed.
> In this case the use of add-method might be the correct course of
> action to take, but be sure to compile the functions that you add, for
> maximum performance, if the methods you add are called more often than
> they are added (which is usually the case).
indeed, they are called thousands of times for a few seconds of
simulation.
But they are only added per class, of which there are only tens of.
>
> BTW in the code you included in your message, you don't need to worry
> about compilation, since the functions you add are _not_ *generated* at
> run-time, but only added at run-time, which means that the functions
> will have been compiled already by the file-compiler.
This is partially true. Infact, if the file is compiled, then a
(eval-when ..)
allows the functions to be added at compile-time as well.
>
> There are a couple of calls of the form (funcall #'bla-bla ...) the
> motivation of which I don't understand, since (bla-bla ...) could be
> used equally well in those places, if I'm not missing something else.
You're not missing anything important. When you say (bla-bla ...)
CMUCL gives tons of warnings that |PCL::MAKE-INSTANCE ...| is not
defined.
I suppose its doing optimization in a sense but I don't quite get what.
>
> > 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).
>
> Sounds great. Good luck with your project and your problem!
>
> Regs, Pierre.
Thanks for the input.
Tunc
Pierre> This seems to be very slow indeed. Running the same code from the
Pierre> listener on CMU CL 18a+ 2.4.15 (Debian Version) on Linux on an
Pierre> AMD-K6-2(350 MHz) I get:
[code snipped]
Pierre> Evaluation took:
Pierre> 0.12 seconds of real time
Pierre> 0.12 seconds of user run time
Pierre> 0.0 seconds of system run time
Pierre> 64 page faults and
Pierre> 163280 bytes consed.
I just ran the code on CMUCL 18b+ (sources from 1999-10-17, with some
personal local mods that shouldn't matter) on an Ultrasparc 30, 300
MHz. I get
Evaluation took:
0.09 seconds of real time
0.07 seconds of user run time
0.0 seconds of system run time
0 page faults and
165872 bytes consed.
An Ultra-30 300 MHz can't be that much faster than an Ultrasparc 2
(0.09 sec vs 3.35 sec)!
Try upgrading to a newer version of CMUCL. If you can't find one, you
can have my copy.
Ray
Tunc
Raymond Toy wrote:
>
> >>>>> "Pierre" == Pierre R Mai <pm...@acm.org> writes:
>
> Pierre> This seems to be very slow indeed. Running the same code from the
> Pierre> listener on CMU CL 18a+ 2.4.15 (Debian Version) on Linux on an
> Pierre> AMD-K6-2(350 MHz) I get:
>
> [code snipped]
>
> Pierre> Evaluation took:
> Pierre> 0.12 seconds of real time
> Pierre> 0.12 seconds of user run time
> Pierre> 0.0 seconds of system run time
> Pierre> 64 page faults and
> Pierre> 163280 bytes consed.
>
> I just ran the code on CMUCL 18b+ (sources from 1999-10-17, with some
> personal local mods that shouldn't matter) on an Ultrasparc 30, 300
> MHz. I get
>
> Evaluation took:
> 0.09 seconds of real time
> 0.07 seconds of user run time
> 0.0 seconds of system run time
> 0 page faults and
Evaluation took:
0.23 seconds of real time
0.23 seconds of user run time
0.01 seconds of system run time
0 page faults and
158800 bytes consed.
#<Standard-Method SLOT-VALUE-USING-CLASS (STANDARD-METHOD
STANDARD-OBJECT
(EQL X)) {701C65D}>
I'm sorry for the confusion.
Thanks,
Tunc
Hidayet Tunc Simsek wrote:
>
> Could it be that I downloaded a binary copy of the distribution?
> I installed this one very recently.
>
> Tunc
>
> Raymond Toy wrote:
> >
> > >>>>> "Pierre" == Pierre R Mai <pm...@acm.org> writes:
> >
> > Pierre> This seems to be very slow indeed. Running the same code from the
> > Pierre> listener on CMU CL 18a+ 2.4.15 (Debian Version) on Linux on an
> > Pierre> AMD-K6-2(350 MHz) I get:
> >
> > [code snipped]
> >
> > Pierre> Evaluation took:
> > Pierre> 0.12 seconds of real time
> > Pierre> 0.12 seconds of user run time
> > Pierre> 0.0 seconds of system run time
> > Pierre> 64 page faults and
> > Pierre> 163280 bytes consed.
> >
> > I just ran the code on CMUCL 18b+ (sources from 1999-10-17, with some
> > personal local mods that shouldn't matter) on an Ultrasparc 30, 300
> > MHz. I get
> >
> > Evaluation took:
> > 0.09 seconds of real time
> > 0.07 seconds of user run time
> > 0.0 seconds of system run time
> > 0 page faults and
have you profiled it to see where the time is going? Adding a method
to any GF will typically cause a bunch of caches to be invalidated
(for instance many of the effective methods &c will need to be
recomputed). I would guess that adding a method to
SLOT-VALUE-USING-CLASS will probably cause an enormous amount of this
to happen, as any naive approach would basically have to invalidate
pretty much *any* use of SLOT-VALUE in the system.
--tim
> > There are a couple of calls of the form (funcall #'bla-bla ...) the
> > motivation of which I don't understand, since (bla-bla ...) could be
> > used equally well in those places, if I'm not missing something else.
>
> You're not missing anything important. When you say (bla-bla ...)
> CMUCL gives tons of warnings that |PCL::MAKE-INSTANCE ...| is not
> defined.
> I suppose its doing optimization in a sense but I don't quite get what.
It is in fact trying to optimize instance creation: When it sees a use
of make-instance with a constant class argument, it will try to use
the class-specific "method" directly, without going through all the
generic dispatch overhead, if possible. But if you reference a class
that has been defined in the same file as the call to make-instance,
the method-function will not yet exist (since the defclass form has
not yet been loaded), and so it warns you about the fact that the
function is not yet defined (it will be defined once you load the
compiled file, i.e. you won't get this warning again when recompiling
the file after having loaded it).
This is a general problem with PCL&CMUCL, which will also give you
spurious warnings on other occassions: Since the CMUCL compiler knows
little about PCL code, it will not know that certain functions will be
defined within the same compilation unit. One way to overcome this is
by causing defclass, defmethod and defgeneric to be evaluated at
compile-time as well, with the following code:
(pushnew 'cl:compile pcl::*defgeneric-times*)
(pushnew 'cl:compile pcl::*defmethod-times*)
(pushnew 'cl:compile pcl::*defclass-times*)
This will cause def* to have compile-time effects as well as load-time
effects, which is a hack, but helps to quelch spurious warnings. I
have been trying to think of a way to make the CMUCL compiler aware of
the special effects of def* transparently, but haven't come up with a
really useable plan until now.
What I wouldn't try to do is to outsmart CMUCL by using funcall, since
this will also throw off any readers of your code IMHO... ;)
To give you an idea on the speed-increases you can expect from this
optimization, here is a snippet of recent CLOS timings I performed on
an AMD-K6-2 350 on Linux with a recent CMU CL version:
MAKE-INSTANCE of a constant class with two slots and no initialization methods:
Takes 10.90 microseconds (average of 100,000 iterations).
MAKE-INSTANCE of a constant class with four slots and no initialization methods:
Takes 13.44 microseconds (average of 100,000 iterations).
MAKE-INSTANCE of a constant class with four slots and one initialization method:
Takes 13.64 microseconds (average of 100,000 iterations).
MAKE-INSTANCE of a variable class by name with two slots and no initialization m
ethods:
Takes 31.18 microseconds (average of 100,000 iterations).
MAKE-INSTANCE of a variable class by object with two slots and no initialization
methods:
Takes 29.04 microseconds (average of 100,000 iterations).
So it will be advantageous to let CMU CL optimize, especially if you
have a high instance creation frequency somewhere (in this case you
could also think about using PCL's defconstructor to reduce creation
times even further...).
Aha! and CL-HTTP has oodles of classes and oodles of SLOT-VALUEs, all
of which are probably ending up needing to be thought about, so it
probably takes ages to smash them all.
--tim