[comp.lang.smalltalk] Performance of "doesNotUnderstand:"

jmaloney@cs.washington.edu (John Maloney) (08/25/89)

I became interested in a recent claim that using the "doesNotUnderstand:
mechanism of Smalltalk to construct "forwarder" objects is a big performance
penalty. Since no numbers were provided, I did a little experiment. I
created a class Forwarder that responded to the message "add:plus:" by
computing the sum of the arguments. I then re-defined the "doesNotUnderstand:"
message for Forwarder to re-direct any messages not understood to itself.
In this case, the forwarder forwards messages to itself, rather than to
a different object, but that should not effect the performance we are
interested in. In a more realistic example there might be some additional
tests of the message selector and argument count before forwarding
the message. These were omitted for simplicity. I then evaluated the
following expression:

| f count t1 t2 t3 |
f _ (Forwarder new).
count _ 10000.
t1 _ Time millisecondsToRun:
	[count timesRepeat: []].
t2 _ Time millisecondsToRun:
	[count timesRepeat: [f bar: 2 plus: 2]].
t3 _ Time millisecondsToRun:
	[count timesRepeat: [f add: 2 plus: 2]].
Array with: (t2 - t1) with: (t3 - t1).

I executed the expression ten times with the following raw results:
(5905 318) (5924 327) (5894 317) (5873 327) (5935 338)
(5914 327) (5904 338) (5893 328) (5925 308) (5935 339)

Note that the first number of each pair is the time in milliseconds to
forward a message 10000 times while the second number is the time to
process the message directly. These times were measured on a ParcPlace 2.3
interpreter running on a Mac SE with a 16 MHz 68020 accelerator
board; the times should be very similar to the performance on a Mac II.

The average time to forward the message was 5910 milliseconds.
The average time to send the message directly was 327.

Thus, forwarding takes roughly 18 times longer than sending a message directly.

But, remember that each loop was repeated 10000 times, so direct message
sends take 0.3 *micro*seconds while forwarding takes 5.9 microseconds.
For comparison, a single floating divide with no coercion takes about
1.1 microseconds on my machine. So, although you wouldn't want to use
message forwarding for everything, I don't believe it should be avoided.

I have seen some very creative uses of "doesNotUnderstand:", such as
Stephen Pope's PropertList object. You can store a property/value pair
in a PropertyList (say, "color: #blue") and then later retrieve the
property by sending it the message "color". In some applications the
flexibility of such a dynamic data structure may be far more important
than a slight decrease in performance. On the other hand, if you are building
a database and must process millions of records at the absolute maximum
speed, you would probably not want to use message forwarding in your
basic data structures!

By the way, "nil"ing the superclass pointer in Forwarder is not a good idea,
since "perform:withArguments:" and many other useful messages are
inherited from Object. What is more, "nil"ing the superclass pointer breaks
the ParcPlace interpreter.

	-- John Maloney (jmaloney@june.cs.washington.edu)

The code for class Forwarder follows.
------------------
Object subclass: #Forwarder
	instanceVariableNames: ''
	classVariableNames: ''
	poolDictionaries: ''
	category: 'hack'!

!Forwarder methodsFor: 'all'!

add: a plus: b

	^a + b!

doesNotUnderstand: aMessage

	^self
		perform: #add:plus:
		withArguments: aMessage arguments! !
------------------

sg04@GTE.COM (Steven Gutfreund) (08/25/89)

I think that tuple-based communications provides a more general scheme for
doing the types of things people are contemplating of doing via forwarding.
I hope to run some experiments in the next few months on a distributed
testbed and be able to get some sort of feel for the level of granularity
I would like to use for multi-processor and distributed processor
implementations. 

For more information on tuple-based Smalltalk see: OOPSLA'88 p. 276
(Matsuoka) or any of Gelernter's Linda papers.
-- 
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
Yechezkal Shimon Gutfreund		 		  sgutfreund@gte.com
GTE Laboratories, Waltham MA			    harvard!bunny!sgutfreund
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

bruce@servio.UUCP (Bruce Schuchardt) (08/28/89)

It was nice to see a concrete example with _real_ timing figures in 
John Maloney's message.  He is right that it is dangerous to nil out
the superclass of a class, but I would not agree with him that you
can't do it at all.  If your "doesNotUnderstand:" method does not break,
there is no reason for the Smalltalk interpreter to take a nose-dive.
A few messages will not go to your "doesNotUnderstand:" method, but
these are handled w/o message sends anyway so you don't have to worry
about them (unless you care that you can't catch them and forward them).
For ParcPlace Smalltalk (v2.3 - I haven't done this work with 2.4 or
2.5 yet), you should consider implementing "refCt" and "==" in your
forwarder class.

There is a lot more I can say on this topic if anyone is interested.
I do want to warn you to (1) Watch the return value
of your "doesNotUnderstand:" method so that you don't return the
object you are forwarding for.  E.g.,

    result _ myObject perform: aMessage selector
               withArguments: aMessage arguments.
    ^ (result == myObject) ifTrue: [self]  ifFalse: [result]

and (2) put code in the debugger to stick the superclass back into
your forwarder class(es) before the context chain is walked to form
the stack list.  This can be done in ProcessHandle in ParcPlace Smalltalk.
-- 
 --------                         --------------
| Bruce Schuchardt          Ph: (503) 629-8383  |
| Beaverton, OR             uunet!servio!bruce  |
 --------------                         --------

abbott@aerospace.aero.org (Russell J. Abbott) (08/30/89)

I've been playing around with Smalltalk for a few weeks now and still
feel like a novice.  As you know, Smalltalk does not have declared
types.  "Type checking" is basically done dynamically: if an object has
a response defined for a message sent to it, then it is of the "right
type."  In trying to understand what that means, I came up with the
following.  I'm sure this must have been worked out properly somewhere.
Does anyone know where?

(In _Smalltalk-80: The Language_, by Goldberg and Robson, the word
"type" does not appear in the index.)

     ----------------------------------------------------------------

In Smalltalk, a type is a set of "messages" that objects of that type
can receive.  That is,

Type ::= {Msg1, Msg2, ..., Msgm}

where a message is a message name, a list of the types of the
parameters, and the type of the result, i.e.,

Msg ::= Name(param1: Type1; param2: Type2; ..., paramn: Typen): Type

The actual type names are not important.  In fact, they are never used
and simply represent this structure.  The message names are important
and play the role of constants of some sort.

The following two definitions are circular (as, I suppose are the
preceding definitions), but I don't see immediately how to fix it.


A message M1 is less demanding than a message M2 if:

(a) M1 and M2 have the same message name and the same number of
parameters;

(b) the type of each of M2's arguments is a specialization of (i.e., is
more specific than, accepts at least the same messages as) the type of
the corresponding argument in M1.  (In other words, M1 requires less of
its arguments than does M2.)

(c) M1's returned type is a specialization of the returned type of M2.
(That is, M1 returns an object that can do at least as much as the
object that M2 returns.)

If M1 is less demanding than M2 then:
if object O1 accepts M1 and object O2 accepts M2 then the call

		O2 M2(P1, P2, ..., Pn)

could be replaced by

		O1 M1(P1, P2, ..., Pn)

and no type conflict would result.


A type T2 is a specialization of (i.e., more specific than) type T1 if
each message in T1 is less demanding than some message in T2.
-- 
-- Russ abbott@itro3.aero.org

johnson@p.cs.uiuc.edu (09/03/89)

The type system that Russ Abbott described is basically the same as
that of Emerald.  There was a paper at OOPSLA 86 about it and some
later papers in IEEE Transactions on Software Engineering.  There
was also a PhD thesis by Norman Hutchinson at the U. of Washington
that described the type system in detail.

A signature based type system will not always work in Smalltalk,
because there are a lot of explicit checks for nil and a few other
explicit checks for the class of objects.  These require a type
system based on discriminated unions.  However, signatures fit in
well with unions.  The type system for Typed Smalltalk uses both.
Discriminated unions are also better for code optimization.

Justin Graver just finished a thesis that describes the type system
for Typed Smalltalk.  The thesis also describes type inference.  The
thesis is still in the process of being copied, but I would be glad to
send anyone a copy if they want it.  The main part of the thesis is
a formal description of the type system, so casual readers beware!

Ralph Johnson