On 24/09/2012 08:06, Nathan Kurz wrote:
2. Nick's proposal
We're defining terms differently here. Yes, the proposal is very
straightforward. The "cognitive load" is that the user needs to be
aware of not only the object as it is, but the full hierarchy. Or am
I misunderstanding how things would work?
Your understanding is correct.
Let me flesh out your example with some silly worst-case pseudocode:
Classes:
class Base {
int id;
int *type;
}
class Parent inherits from Base {
int field;
int order;
}
class Child inherits from Parent {
int number;
int subtype;
}
class MyChild inherits from Child {
int result;
int subfield;
}
NickNew:
int MyChild_prepare_result(MyChild *self) {
Base *base_ivars = Base_IVARS(self);
Parent *parent_ivars = Parent_IVARS(self);
Child *child_ivars = Child_IVARS(self);
MyChild *mychild_ivars = MyChild_IVARS(self);
if (parent_ivars->field = 1 && mychild_ivars->subfield == 2) {
mychild_ivars->result = child_ivars->order;
}
else {
mychild_ivars->result = base_ivars->type[parent_ivars->number];
}
return base_ivars->id;
}
I think this matches what is proposed? If so, even the scrolling back
and forth to get this example right was painful. If it involved
flipping back and forth between 4 different files (3 of which are
unfamiliar) it would be excruciating.
Along that lines, I think I left at least one error in "New" -- how
long does it take to spot it?
I think your example is a bit extreme. In many methods we'll only access
the ivars of a single class, and I guess that there are only very few
cases where we need access to more than two classes.
We also want to discourage writing code that relies on access to ivars
of parent classes. Unless it's a really performance-critical part, I'd
rewrite the method above to use accessors:
int MyChild_prepare_result(MyChild *self) {
MyChild_IVARS *ivars = MyChild_IVARS(self);
if (MyChild_field(self) == 1 && ivars->subfield == 2) {
ivars->result = MyChild_order(self);
}
else {
int *type = MyChild_type(self);
ivars->result = type[MyChild_number(self)];
}
return MyChild_id(self);
}
My second "complaint" is that despite this, we still end up with base
classes that are quite fragile. The benefit (and it is significant)
is that we gain the ability to add member variables to the end of the
structs for each parent class. But programmer discipline is needed,
for if they are added elsewhere compiled modules will break even
though the local recompile works just fine. Moving a variable from
Grandparent to Parent causes the same problems. Deletion is always
going to be tricky, but it would be nice to get an error message
rather than a segfault. If we are going down this route, I think we
should aim for truly robust rather than just less fragile.
Note that my approach doesn't allow access to ivars of other parcels. So
within a parcel, we can move and even delete ivars at will.
On the bright side, I don't think that performance is going to be that
big of an issue. Maybe 50 cycles per function for the accessing a
variable once the lookups are cached in L3? Another dozen for the
pipeline to finish adding the offsets? Repeated access should be much
faster, and we can easily cache a local pointer for tight loops.
Taking a blind stab, maybe 25% initially which we can reduce to 10% by
hitting a few hotspots? Making it fast again while gaining the
flexibility seems like a fun challenge.
It's just a guess, but I think in most cases the global offset can be
fetched from L1, so the performance hit should only be around 5 cycles
per method call (double that if we have to go through the GOT). It's the
same performance hit that we already take for virtual method dispatch.
There will be added register pressure though, which might be noticable
on x86-32.
3. Alternative Proposal
I think we're going a bit in circles here. Your proposal looks very much
like Marvin's proposal in the first message of this thread (one offset
global per ivar).
If you're only concerned with making access of parent class ivars more
convenient, we could achieve this with a couple of macros on top of my
proposal:
#define Sub_sub_num(self) Sub_GET_IVARS(self)->sub_num
#define Sub_sub_float(self) Sub_GET_IVARS(self)->sub_float
#define Sub_base_num(self) Base_GET_IVARS(self)->base_num
#define Sub_base_float(self) Base_GET_IVARS(self)->base_float
If I understand your proposal correctly, this should result in basically
the same compiled code.
Nick