Re: Zeroing out instance variables
Re: Zeroing out instance variables
- Subject: Re: Zeroing out instance variables
- From: Ben Haller <email@hidden>
- Date: Sat, 17 Apr 2010 09:34:26 -0400
On 17-Apr-10, at 7:01 AM, Ken Thomases wrote:
On Apr 16, 2010, at 4:05 PM, Ben Haller wrote:
So I'm keeping an "unused pool" of these objects, and when I'm done
with an object I throw it into the pool, and when I need a new one
I grab one from the pool. I do these operations with inline
functions, so I can get a new object instance very quickly indeed.
This works great, and I've been doing it for a while. Of course -
init only gets called when the object is truly allocated, the first
time around.
Actually, I would think you'd be better off treating an unused
object as truly deallocated and then you'd want to re-init it when
it is allocated again.
In other words, I'd think you would want a memory pool, not an
object pool. You should be able to implement that by overriding
+allocWithZone: and -dealloc to use a custom allocator instead of
falling through to either super's implementation or NSAllocateObject/
NSDeallocateObject.
Your +allocWithZone: would reuse memory from your pool in preference
to allocating it anew. Then, it would set the 'isa' ivar and bzero
the rest. It would -retain the object before returning it. Your -
dealloc would just return the object's memory to the pool. All done.
An interesting idea, but would be much slower than my scheme. Four
method calls (release, dealloc, alloc, retain) per recycled object,
instead of a few lines of inline code.
That said, if you really want to go with the object pool rather than
the memory pool, there's no particular reason to be especially
careful about the use of -init. An object all of whose ivars other
than 'isa' are zeroed is indistinguishable from one which hasn't
been init-ed. So, there's no particular reason to avoid calling -
init on it each time it is reused. The -init method isn't magical
nor mysterious. It's just a normal method like any other, but with
some conventions surrounding its use.
Not a big deal. Just thought I'd point it out, since you made the
point about only calling -init once.
Well, this makes assumptions about what NSObject is doing in its
ivars, no? If I bzero NSObject's ivars, I might be stomping something
that I'm not allowed to stomp. If I don't bzero NSObject's ivars, but
do call NSObject's -init twice for the same object, that could also
violate the design of NSObject; in fact I am fairly certain that I
have read that you are not allowed to do that, but I can't seem to
find it in the docs now. I think this is a flaw in Paul Sanders'
scheme, too, now that I think about it; assigning from an inited
object blows away whatever was in NSObject's ivars, which really
doesn't seem OK. In the current implementation it may be just the isa
pointer, IIRC, but there is no reason to assume that will always be
the case.
If I avoided calling NSObject's -init, then yes, there is nothing
"magical nor mysterious" about -init. :-> But that would mean having
two sets of -init methods, one for the "original" case, and one for
the "reuse" case.
The question is how to zero out the ivars correctly. I have the
Class of the object I'm reusing, and I can do the math ahead of
time once. But the class is not fixed at compile time; it depends
upon choices the user makes at runtime. So I have to use the
Objective-C runtime to find my ivar block and zero it out. The
first ivar in the class is known, because it is defined by the
common superclass of all of these objects; it's called "pedigree".
So what I'm thinking of doing is:
1. individualIvarsOffset =
ivar_getOffset(class_getInstanceVariable(individualClass,
"pedigree"));
2. individualIvarsSize = class_getInstanceSize(individualClass) -
individualIvarsOffset;
3. bzero(individual + individualIvarsOffset, individualIvarsSize);
Step one gets the offset of the known first instance variable.
This seems safe to me as long as the ivar layout is guaranteed not
be shuffled around arbitrarily; i.e. as long as variables occur in
memory in the order in which they are declared in the header file.
Is that a guarantee that Obj-C gives, or not?
Not quite, but I don't think you need it. (The non-fragile instance
variable implementation, as well as truly synthesize declared
properties, require flexibility in the order of instance variables.)
First, your classes are direct subclasses of NSObject. NSObject is
documented to only have the 'isa' instance variable. So, you don't
need to find your first instance variable, you only need to avoid
NSObject's isa ivar. You can zero out everything else.
Oh, if it is guaranteed to only have "isa" then that does simplify
things; never mind what I say above, then. Where is that documented?
The guarantee you do get is that the superclass's instance variables
are all before the subclass's. This would be most people's
expectation; it's also implicit in the design of runtime API. For
example, if you get an 'Ivar' reference from
class_getInstanceVariable(), then its offset, as given by
ivar_getOffset(), can only be dependent on the class passed into the
former. There's no way for the runtime to provide a different
offset for subclasses.
Step two calculates the size of the ivars block that I own (i.e.
not NSObject's ivars, which I certainly don't want to touch; just
the ones for my subclasses) as the total instance size minus the
offset of the known first instance variable. This seems safe given
the same caveat as step one, plus the added caveat that no extra
stuff can be added to the object's memory block at the end;
everything from my first ivar to the end of the malloced block must
belong to me. Again, I'm not sure if this is a guarantee Obj-C
gives...?
Well, there's the weird, rarely-if-ever used extraBytes parameter to
NSAllocateObject()/class_createInstance(). It's accessible with
object_getIndexedIvars(). But, still, you're safe because it's not
included in class_getInstanceSize(). How could it be since it's a
per-instance thing while class_getInstanceSize() only takes the class?
And, as I say, since you're guaranteed that the instance variables
of the superclass precede those of the subclass, your calculation
can be class_getInstanceSize(individualClass) -
class_getInstanceSize([individualClass superclass]).
(Alternatively, you can specify [NSObject class] in that last call,
given that you're deriving directly from NSObject.)
Furthermore, you can use class_getInstanceSize([individualClass
superclass]) as the value of individualIvarsOffset, too. As I say,
you don't really care about specific ivars, you just care about
blocks of ivars belonging (or not) to a given class.
Ah, I like this better than my code. Very good, thanks.
So, thoughts? Am I insane? Is the above scheme safe? Is there a
better way? Thanks for any feedback!
Not insane, although, as Graham suggested, hopefully you've measured
and have good reason to believe your scheme is really faster than
the built-in allocator.
I have.
Also, how do you know when your objects are "free" to be reused?
With the built-in allocator or the memory pool approach, you know
you're not reusing objects while they're still in use somewhere.
The objects are owned by a single object in my app. When that
object is done with them, it knows, and it throws them back into the
unused pool. If someone else kept a reference to it, then yes, there
would be a problem; but that does not happen, and it would be a
violation of the architecture.
Lastly, if your objects are so simple (direct subclasses or
NSObject, no pointer ivars, etc.), have you considered using plain C
structs instead of objects? Or C++ objects and collections? I'm
not saying you necessarily should use those, but you should consider
them if performance is so critical and you're bending over backward
to work against the normal Cocoa way.
Yeah, I thought about that a lot, actually. It's a weird
situation, but I really do want and use inheritance and dynamic
message dispatch and so forth. It only comes into play a few times in
the entire lifetime of each individual object, but at those few
crucial junctures, it would be a huge PITA to be doing things the
struct way. C++ hadn't occurred to me, but I'm not fond of the
language and don't really want to open that can of worms; to me, that
would be uglier than what I'm doing now, in fact. Is there a reason
that I'm missing, why that would be a good solution?
Ben Haller
McGill University
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden