MS: While playing with tcl-only OO, an important design decision concerns the method used for dipatching the calls to the different object methods. The speed of the system, and ultimately its usability, is strongly dependent on the speed of the dispatcher.
So I decided to time the different possible methods (marked with *) and contrast the timings with plain proc calls.
In order to allow dynamic class membership, the complete path to any object method cannot be fixed at compile time. It hence has to be looked up at runtime. The dispatcher's task is to do this lookup for arbitrary method calls, and call the correct method.
Let me describe a couple of variants that occurred to me (and kindly request any other suggestions!). The framework is as follows:
We focus here on the speed of dispatching; a different (less important?) concern is the speed at which class membership relationships can be updated. Another concern that we disregard here is the memory requirements for each object.
The methods that we compare here are:
If anyone ever wants to update this, then namespace ensemble and tclOO dispatch should be added too.
DKF: I expect that ensembles will be similar to interp alias, and that TclOO's normal proc dispatch will be a bit slower than a procedure call. Whatever that means.
Remark that methods 1-5 call the target proc ONLY; they do not need to eval their args beforehand. Methods 6 and 7 on the other hand have to eval.
NOTE: this has been modified to make it fairer and easier to read: (a) the evaling methods now eval a pure list (faster!); (b) we use time instead of a for loop - less loop overhead, purer measurement and result is directly in [microseconds per call]; (c) added for comparison the [lookup] and [noop] results; (d) increased the runs to 30000, which produces much stabler results on my machine.
How the methods scale for deeper inheritance may also be interesting; not done here.
On my notebook (P166, linux, tcl8.4a2) the timings for the script below are:
% source sent_corr
direct | 16 microseconds per iteration |
import | 16 microseconds per iteration |
interp alias | 51 microseconds per iteration |
indirect | 67 microseconds per iteration |
array | 27 microseconds per iteration |
switch | 99 microseconds per iteration |
lookup | 223 microseconds per iteration |
inline | 7 microseconds per iteration |
noop | 3 microseconds per iteration |
HK: Tried this benchmark on a Sun Blade 1500 running on tcl8.4.6. Here are the timings:
direct | 2 microseconds per iteration |
import | 3 microseconds per iteration |
interp alias | 3 microseconds per iteration |
indirect | 10 microseconds per iteration |
array | 3 microseconds per iteration |
switch | 13 microseconds per iteration |
lookup | 24 microseconds per iteration |
inline | 1 microseconds per iteration |
noop | 0 microseconds per iteration |
Apparently the interp alias method has been improved a bit, now being a viable alternative.
# The code
namespace eval dad { proc test {x args} {set x} test 5 namespace export * } namespace eval son {} proc t {n} { # 1. Plain proc call proc ::son::test {x args} {set x} ::son::test 5 set t [time {::son::test 5} $n] namespace eval :: {rename ::son::test {}} puts " direct : $t" # 2. namespace import namespace eval ::son namespace import -force ::dad::* set t [time {::son::test 5} $n] puts " import : $t" # 3. interp alias interp alias {} ::son::test {} ::dad::test set t [time {::son::test 5} $n] interp alias {} ::son::test {} puts " interp alias : $t" # 4. indirect through proc proc ::son::test {x args} {eval [linsert $args 0 ::dad::test $x]} ::son::test 5 set t [time {::son::test 5} $n] namespace eval :: {rename ::son::test {}} puts " indirect : $t" # 5. store in array (it's name is the empty string!) set ::(::son::test) ::dad::test set t [time {$::(::son::test) 5} $n] puts " array : $t" # 6. switch proc ::son {method args} { switch $method { a {} b {} test {return [eval [linsert $args 0 ::dad::test]]} } } ::son test 5 set t [time {::son test 5} $n] namespace eval :: {rename ::son {}} puts " switch : $t" # 7. lookup set ::b [list ::dad ::none] proc ::son {method args} { foreach anc $::b { if {[llength [info proc ${anc}::$method]]} { return [eval [linsert $args 0 ${anc}::$method]] } } } ::son test 5 set t [time {::son test 5} $n] namespace eval :: {rename ::son {}} puts " lookup : $t" # 8. inline set x 5 set t [time {set x} $n] puts " inline : $t" # 9. empty loop set x 5 set t [time {} $n] puts " noop : $t" } t 30000