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

Reply via email to