An idea I had was for each function to create a small dispatch table, where each function slot also was a tree node.
typedef struct Slot
{
SEL selector;
IMP function;
struct Slot* left;
struct Slot* right;
} Slot;
With the class looking like this:
typedef struct Class
{
// ... Other stuff
Slot slot[FUNCTION_SLOTS];
// ... More stuff
} Class;
For dispatch we can now do:
Slot* slotPtr = object->class->slot + selector & SEL_HASH;
do
{
if (slotPtr->selector == selector) return slotPtr->function(object, selector);
slotPtr = (slotPtr->selector > selector) ? slotPtr->left : slotPtr->right;
} while (slotPtr);
// ... execute MethodNotFound:
This means a worst case O(log(N)) lookup where N is the maximum number of methods hashed to the same slot.
My tests seem to indicate that it is surprisingly fast, at least when inlining the dispatch method:
Best case: 207 million sends/s.
Worst case 6<N<11: 138 million sends/s
Direct dispatch table call: 234 million sends/s.
I'm not sure how reliable these benchmarks are, but it would be interesting to compare these results to the algorithm in the Objective-C runtime.
The downside of course is that all classes need to keep all methods they respond to in the dispatch table, as opposed to only the methods they override.
[Edit:]
Here are results of microbenchmarks on the Obj-C runtime (smaller is better):
Virtual call 1.00
Best case inlined call 1.13
Best case non-inlined 1.77
Best case lookup 1.82
Worst case inlined call 1.70
Worst case non-inlined 2.30
Worst case lookup 2.44
Objective-C OS X 10.5 1.77
Note that the Objective-C result gives the best case for the runtime.
0 comments:
Post a Comment