One of the things that make dynamic binding an 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.
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 nil 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 nil:
All forwardDelegate selector sel { = nil; } |
(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]; } |
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,
[foo do (1, 2)]; |
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]; } |
Table 6-3. Speed of method invocation
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 |
In Table 6-3, using the morerows attribute causes jade/html to produce incorrect tables, and jade/tex to die.
In Table 6-3, `how' describes how the method is invoked, `#inv' is the number of invocations performed in the given time, and `time' is 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 InvocationResultis 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.)