Re: Accessing buffers in NSData/NSMutableData under garbage collection
Re: Accessing buffers in NSData/NSMutableData under garbage collection
- Subject: Re: Accessing buffers in NSData/NSMutableData under garbage collection
- From: Alastair Houghton <email@hidden>
- Date: Tue, 19 Feb 2008 15:03:35 +0000
On 19 Feb 2008, at 14:37, Rick Hoge wrote:
1) The code fragment in my original post *will* break if
optimization is turned on, but not if it is off:
-(IBAction)danglePointer:(id)sender {
int size = 1e6;
NSMutableData *testData = [NSMutableData
dataWithLength:sizeof(float)*size];
float *testPointer = (float*)[testData mutableBytes]; // Note that
__strong makes no difference here
[[NSGarbageCollector defaultCollector] collectExhaustively];
sleep(1); // If there is no delay, I was not able to get a crash
(in 20-30 trials)
BOOL flag = YES;
if (flag) {
testPointer[0] = 3; // If optimization is turned on, the code
crashes at this line every time
NSLog(@"testPointer = %f",testPointer[0]);
}
}
[snip]
Note also that it's clearly not just scope rules that determine when
collection can take place, since testData is still in scope at the
crashing line of code.
Indeed. The problem is that the compiler has elided the testData
variable. If you add another use of the testData variable after the
"if (flag)" statement, you'll find that it will stop crashing.
Note also that using __strong in front of the pointer declaration
makes no difference, since the collector cares about references to
the object and not to its internal structures.
Using __strong would make no difference in *any* case because it does
nothing for local variables. The collector *always* scans them and
they can't contain intergenerational pointers (by definition), so
there would be no point in a write barrier.
The above snippet (with delay) better represents my real apps, in
which there may be a lot of processing work done between
instantiation of the NSMutableData and later use of the pointer. In
my haste to put together a tiny test case, I built and ran it in
"Debug" mode the first time (optimizations off), which appears to be
why I couldn't get the crash. Of course replacing the two lines
including testPointer[0] with
*((float*)[testData mutableBytes] + 0) = 3.0;
NSLog(@"testPointer = %f",*((float*)[testData mutableBytes] + 0));
runs fine under optimization.
Indeed. All you have to do is make sure that the compiler hasn't
removed all references to the object. Sticking a "volatile" on the
NSMutableData pointer, i.e.
NSMutableData * volatile testData = [NSMutableData
dataWithLength:sizeof(float)*size];
(N.B. *after* the '*', not before) will force the matter.
2) NSAllocateCollectible() is probably a more appropriate choice in
the above example. I have tested a number of scenarios running the
Object Allocation template in Instruments to test apps with large
blocks allocated in this way, and the memory footprint of the app
appears to be controlled quite effectively under GC.
It has a similar problem to NSMutableData if you're using it this
way. That is, you need to keep hold of a pointer to the start of the
buffer, and you mustn't allow the compiler to optimise it away.
3) You can declare pointer instance variables using the __strong
qualifier and these can point to blocks allocated with
NSAllocateCollectible(). The memory will live as long as the
object, and is freed when the object is reclaimed (sorry if this is
obvious, but I had to test it to be sure myself). I was able to
create a GC doc-based apps in which the docs 'own' 100MB memory
buffers allocated in the above way - these have the appropriate
lifetime and are reclaimed at the appropriate time.
Yes.
Having realized the above points, this gets back to part of my
original question regarding programming style (rather than
correctness) under GC. On this note I'm still trying to figure out:
1) What potential pitfalls might arise if I start using
NSAllocateCollectible to create buffers where I used to use NSData
objects? The obvious differences, which may not matter depending on
the app, seem to be:
- can't put the buffer into a collection (NSArray, NSDictionary,
etc.) as easily as an NSData/NSMutableData
- less obvious how KVC should work
(before, if I updated an NSData or NSMutableData object, I'd use
a setXXX method and KVC would just work.
with a buffer, I could probably use -willChangeValueForKey:/-
didChangeValueForKey: so this is probably not a huge deal)
- lose the ease of doing a -copy and -mutableCopy to replicate the
memory chunk (not much more work with a buffer, though)
- lose the serialization and archiving methods of NSData/
NSMutableData
You still have the problem that the compiler might elide the pointer
under optimization. You need either to use "volatile" or to store the
pointer into a global or instance variable that has been marked
__strong.
2) As mentioned above, there may be situations where objects want
to be notified when the data contained in a buffer allocated with
NSAllocateCollectible() (and referenced by a pointer instance
variable in another object) changes. One simple approach would be
to make sure code that modifies the memory is wrapped with -
willChangeValueForKey:/-didChangeValueForKey: calls. This will
almost surely work, but I wonder if this is considered good style
(since I may not really be changing the pointer value, just values
in the memory that it points to). Any comments on this or
suggestions for better ways would be welcome.
I think what you propose here is pragmatic; it also matches the kind
of behaviour you'd expect to see if you had e.g. an NSMutableString
instance variable, surely?
But I'd stick with NSData, I think. It seems to have advantages and
no significant disadvantages.
I would write more, but my hand is hurting again :-(
Kind regards,
Alastair.
--
http://alastairs-place.net
_______________________________________________
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