One of the things that make dynamic binding a very interesting
approach to method dispatching (i.e., the decision which code to
invoke to handle a given message) is the ability to forward a
method invocation. A message is forwarded when the receiver
object does not provide an implementation of the method denoted by
the message. In this highlight, the forward mechanism of TOM will
be explained.
Every method has two implicit arguments: self and
cmd . The object self is the `current
object'; it is the receiver of the message that lead to the method
invocation. The argument cmd is the selector of that
message (see highlight `The selector
type').
When an object does not implement a method, the self and
cmd arguments are used to decide how to respond to
the message.
- The receiver is asked for its
forwardDelegate . If an
object other than self is returned, the message is
simply resent to that object. This is the mechanism of choice
when a lot of methods are to be delegated to few different
objects.
The forwardDelegate method is implemented by the
instance All , and thus (by convention) implemented by
all classes and instances. The default implementation simply
returns self :
|
All (self)
forwardDelegate selector sel
{
}
|
- (Skip this item if you don't understand it.) If the object implements
the method
|
InvocationResult
forwardSelector selector sel
arguments pointer pap;
|
then that method is invoked, with sel being the
selector of the message being forwarded, and pap a
pointer to a va_list for the arguments in the
message. The InvocationResult that is returned
defines the values to be returned from the invocation that is
being forwarded.
Note that an object implementing a method foo is different
from an object respoding TRUE when asked
respondsTo . The latter can be overridden while the
former is a direct check, which is much faster.
This seemingly nasty low-level approach is used for fast dispatching of
too.RemoteProxy method invocations and for
invocations on curried tom.Invocation objects.
- With everything having failed so far, the whole invocation is
packed into a newly created
Invocation object, and
sent to the receiver with a forwardInvocation method.
The receiver can then decide what to do with it. The default
implementation by the instance All raises a
program-condition for the selector and target of the
Invocation :
|
InvocationResult
forwardInvocation Invocation invocation
{
[[SelectorCondition
for self class program-condition
message "unrecognized selector"
selector [invocation selector]]
raise];
}
|
The forwardInvocation mechanism can also be used to
mimic the functionality of the forwardDelegate
method. If the forwardDelegate would return the
object my_delegate , the following method would
provide identical functionality:
|
InvocationResult
forwardInvocation Invocation invocation
{
= [invocation fireAt my_delegate];
}
|
Speed
Everything comes with a price, especially flexibility.
I've done some speed tests (on a PII/266, Debian 1.3.x, gcc 2.7.2.1)
with various ways to invoke a method, with various number of
arguments, with the results shown in the table below.
- `x' is a direct method invocation,
- `p' is a perform
|
[foo perform selector ("v_do_(ii)") with (1, 2)];
|
- `d' is through a
forwardDelegate . The invocation looks
like that of `x', but it is invoked on an object that does not
implement the method, but does provide the following method (an
instance of Sub does implement the desired method).
|
Sub
forwardDelegate selector s
{
= fwd_delegate;
}
|
- `i' is through a
forwardInvocation . Similar to `d', but
instead of forwardDelegate , the following method is
implemented:
|
InvocationResult
forwardInvocation Invocation invocation
{
= [invocation fireAt fwd_delegate];
}
|
how |
#inv |
time |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
x |
10^8 |
12.58 |
12.93 |
12.58 |
12.53 |
13.69 |
14.00 |
13.91 |
p |
10^7 |
11.15 |
11.77 |
12.74 |
13.80 |
14.88 |
15.63 |
17.32 |
d |
10^7 |
11.62 |
12.75 |
13.75 |
15.11 |
15.81 |
16.61 |
18.52 |
i |
10^6 |
15.96 |
17.44 |
17.80 |
18.14 |
18.51 |
18.87 |
19.22 |
Legend:
how | How the method is invoked |
#inv | The number of invocations performed in the
given time. |
time | The CPU time needed to run the test for the
indicated number of arguments. |
Note that for every forwardInvocation dispatch, an
Invocation object and an
InvocationResult is created, and approximately half
the time taken by the test runs is spent in garbage collecting
those objects. Also note that the mechanism underlying
perform with and forwardDelegate , does
not create InvocationResult objects for void methods,
as was the case in the test.
The numbers come down to the following: in the time that you can do 1
forwardInvocation , you can do 10
forwardDelegate calls or invocations through
perform with , and 100 direct method invocations.
I don't know if these numbers are good or bad; I don't have numbers to
compare them with (maybe testing Objective-C Rhapsody on a
PII/266?). It does show, that using forwardDelegate
is much faster than having an Invocation object be
created and forwarding that. Which is what it was supposed to do.
(And it can probably be made much faster when using
__builtin_apply_args in trt_forward ,
instead of going through perform_args .)
Up: Highlights
|