On Thu, Sep 11, 2014 at 3:44 AM, Nick Wellnhofer <[email protected]> wrote:
> On 11/09/2014 03:20, Marvin Humphrey wrote:
>>
>> +1, this is a better solution. It should allow NULLs, though.
>>
>> if (obj != NULL && !obj->klass->itables[Futzer_INTERFACE_ID]) {
>
> Yes, and it should also INCREF the result.
I'm skeptical about having a cast operation trigger a refcount increment.
Besides the runtime cost of the refcount manipulation (or interface object
allocation), the need to DECREF after use also makes programming more complex
and increases the likelihood of memory leaks.
static int
compare(const void *va, const void *vb) {
Comparer *a = toCOMPARER(*(Obj**)va);
Comparer *b = toCOMPARER(*(Obj**)vb);
int retval = Comparer_Compare_To(a, b); // exception here causes leak
DECREF(a);
DECREF(b);
return retval;
}
I understand that avoiding refcount manipulation narrows our implementation
options.
> I'm still concerned about the quadratic space complexity. It shouldn't be a
> problem unless there are thousands of classes and interfaces but here are
> some numbers to illustrate the non-linear growth of the itable arrays:
>
> 50 classes, 20 interfaces: 8 KB
> 500 classes, 200 interfaces: 800 KB
> 5000 classes, 2000 interfaces: 80 MB
It's a reasonable concern.
> Some other ideas (simply brainstorming):
Here's an article on how Mono implements interface method dispatch:
http://monoruntime.wordpress.com/2009/04/22/interface-method-dispatch-im-table-and-thunks/
It influenced my thinking on lazy loading.
> 1. Allocate the itable array on demand. Objects of many classes are never
> converted to interface types. This would only require an additional NULL
> check.
Right, and the NULL check is in the cast which is better than having it in the
method dispatch routine.
We could do this and also divide the itable array into, say, 8 parts. Then
during the lookup, figure out which array to look in by performing a
mask/shift on the interface ID.
Hey, how about we encode the interface ID and into the high bits of the method
OFFSET variable? Then there's only one global variable memory fetch needed
during method dispatch.
static inline void
Futzer_Futz(Futzer *self) {
uint32_t offset = Futzer_Futz_OFFSET;
uint32_t itable_array_slot
= (offset & ITABLE_ARRAY_MASK) >> ITABLE_ARRAY_SHIFT;
Interface **itables = &self->klass->itables + itable_array_slot;
uint32_t interface_id
= (offset & INTERFACE_ID_MASK) >> INTERFACE_ID_SHIFT;
Interface *interface = itables[interface_id];
char *ptr = (char*)interface + (offset & IMETHOD_OFFSET_MASK);
Futzer_Futz_t method = (Futzer_Futz_t)ptr;
method(self);
}
Oh, and OFFSET vars should probably be uint32_t rather than size_t. No class
is ever going to have so many methods that we need a size_t. That'll save
some space on 64-bit systems.
Marvin Humphrey