garbage collection and NSConnection
-
hey,
I have a project that uses Bonjour for some of its communication,
theres a server and a client, and I was having tremendous difficulty
getting it to work, pouring and pouring over my code, only to discover
some weeks later that for some odd reason, NSConnections do not work
when the project is set to support or require garbage collection.
As a test I set garbage collection to: Unsupported, and the app
compiled, and the NSConnection returned the proxy object as expected.
But the app obviously failed to do much else, because I had no retain,
release, or autorelease method calls.
I am not a lazy person, but I am a novice programmer, and the retain,
release, autorelease stuff in cocoa is horrible. I was very happy when
garbage collection was added to Cocoa, and my projects became much
easier to develop, and maintain. Now however, I find myself with a
project riddled with memory problems that did Not exist just a few
days ago, and as far as I can tell, I don't have any choice... My app
either gives up Bonjour, or I have to retrofit the whole thing to
manage its own memory with a system that is, lets face it, poorly
envisioned.
can anybody shed any light on this? am I really stuck managing the
memory myself? This is intolerable.
cheers,
-eblu -
Hi there-
I encountered the same issue some months ago, and posted my questions
to this list. An Apple engineer did reply off-list that this was a
known issue with garbage collection and that there was no known
workaround at that time.
I was just playing with GC for fun and reverted back to "regular"
memory management.
You might inquire of Apple DTS if things have changed at all; my
incident was in the 10.5.0 days.
John
John Pannell
http://www.positivespinmedia.com
On Jun 30, 2008, at 11:33 AM, <eblugamma...> wrote:> hey,
> I have a project that uses Bonjour for some of its communication,
> theres a server and a client, and I was having tremendous difficulty
> getting it to work, pouring and pouring over my code, only to
> discover some weeks later that for some odd reason, NSConnections do
> not work when the project is set to support or require garbage
> collection.
>
> As a test I set garbage collection to: Unsupported, and the app
> compiled, and the NSConnection returned the proxy object as
> expected. But the app obviously failed to do much else, because I
> had no retain, release, or autorelease method calls.
>
> can anybody shed any light on this? am I really stuck managing the
> memory myself? This is intolerable. -
Hi Chris,
I'm not terribly sure what you are asking for here. From my
experience (limited experience admittedly) theres really only one way
to use NSConnection.
its a pretty elegant class, which is simple, and works as expected,
except for when garbage collection is enabled.
heres what I do just after NSNetService finds a service:
// Sent when a service appears
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didFindService:(NSNetService *)aNetService
moreComing:(BOOL)moreComing
{
NSMutableDictionary* newDict = [[NSMutableDictionary alloc] init];
[newDict setValue:aNetService forKey:@"theService"];
[serverArrayController addObject:newDict];
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:5];
if(!moreComing)
{
}
}
// NSNetService Delegate method:
- (void)netServiceDidResolveAddress:(NSNetService *)sender{
id proxy = nil;
NSData *addy;
NSSocketPort* socket;
NSConnection* connection;
NSString* hostname;
int a;
int i;
hostname = [sender hostName];
socket = (NSSocketPort*)[[NSSocketPortNameServer sharedInstance]
portForName:@"BKOtherPort" host:hostname];
connection = [NSConnection connectionWithReceivePort: nil sendPort:
socket];
@try{
proxy = [connection rootProxy];
}
@catch(id exception){
proxy = nil;
}
addy = [socket address];
if(proxy){
// app level stuff if the proxy exists
}
}
pretty straight forward,
and every time I ran it with garbage collection on, the NSConnection
initialized, but NEVER returned the proxy. it returned nil.
all my instance variables were populated, everything on My end was
correct... or at least behaving as expected. it just wouldn't return
the proxy object (or the root for that matter)
All I did to fix it, was to turn off garbage collection.
That part runs like a champ now. the rest of the app won't do
anything anymore, as it was built on garbage collection.
cheers,
-eb
On Jun 30, 2008, at 3:37 PM, Chris Hanson wrote:> On Jun 30, 2008, at 10:33 AM, <eblugamma...> wrote:
>
>> only to discover some weeks later that for some odd reason,
>> NSConnections do not work when the project is set to support or
>> require garbage collection.
>
> How are you using NSConnection? NSConnection and Distributed
> Objects definitely *does* work with Objective-C garbage collection,
> at least in the situations in which I've used it.
>
> Some code examples might be illustrative.
>
> -- Chris
> -
Le 30 juin 08 à 22:10, eblu a écrit :> Hi Chris,
> I'm not terribly sure what you are asking for here. From my
> experience (limited experience admittedly) theres really only one
> way to use NSConnection.
> its a pretty elegant class, which is simple, and works as expected,
> except for when garbage collection is enabled.
>
> heres what I do just after NSNetService finds a service:
>
> // Sent when a service appears
> - (void)netServiceBrowser:(NSNetServiceBrowser *)browser
> didFindService:(NSNetService *)aNetService
> moreComing:(BOOL)moreComing
> {
> NSMutableDictionary* newDict = [[NSMutableDictionary alloc] init];
> [newDict setValue:aNetService forKey:@"theService"];
>
> [serverArrayController addObject:newDict];
> [aNetService setDelegate:self];
> [aNetService resolveWithTimeout:5];
> if(!moreComing)
> {
> }
> }
>
> // NSNetService Delegate method:
> - (void)netServiceDidResolveAddress:(NSNetService *)sender{
>
> id proxy = nil;
> NSData *addy;
> NSSocketPort* socket;
> NSConnection* connection;
> NSString* hostname;
>
> int a;
> int i;
>
> hostname = [sender hostName];
> socket = (NSSocketPort*)[[NSSocketPortNameServer sharedInstance]
> portForName:@"BKOtherPort" host:hostname];
> connection = [NSConnection connectionWithReceivePort: nil sendPort:
> socket];
> @try{
> proxy = [connection rootProxy];
> }
> @catch(id exception){
> proxy = nil;
>
> }
> addy = [socket address];
> if(proxy){
> // app level stuff if the proxy exists
> }
> }
>
> pretty straight forward,
> and every time I ran it with garbage collection on, the NSConnection
> initialized, but NEVER returned the proxy. it returned nil.
> all my instance variables were populated, everything on My end was
> correct... or at least behaving as expected. it just wouldn't return
> the proxy object (or the root for that matter)
> All I did to fix it, was to turn off garbage collection.
> That part runs like a champ now. the rest of the app won't do
> anything anymore, as it was built on garbage collection.
>
> cheers,
> -eb
>
And did you try using CFNetServices instead ? As it is CF based, it
probably does not have the same GC problem and is it far more easier
to replace NSNetService by CFNetService than rewriting the whole
application without GC . -
Le 30 juin 08 à 22:19, Jean-Daniel Dupas a écrit :> Le 30 juin 08 à 22:10, eblu a écrit :
>
>> Hi Chris,
>> I'm not terribly sure what you are asking for here. From my
>> experience (limited experience admittedly) theres really only one
>> way to use NSConnection.
>> its a pretty elegant class, which is simple, and works as expected,
>> except for when garbage collection is enabled.
>>
>> heres what I do just after NSNetService finds a service:
>>
>> // Sent when a service appears
>> - (void)netServiceBrowser:(NSNetServiceBrowser *)browser
>> didFindService:(NSNetService *)aNetService
>> moreComing:(BOOL)moreComing
>> {
>> NSMutableDictionary* newDict = [[NSMutableDictionary alloc] init];
>> [newDict setValue:aNetService forKey:@"theService"];
>>
>> [serverArrayController addObject:newDict];
>> [aNetService setDelegate:self];
>> [aNetService resolveWithTimeout:5];
>> if(!moreComing)
>> {
>> }
>> }
>>
>> // NSNetService Delegate method:
>> - (void)netServiceDidResolveAddress:(NSNetService *)sender{
>>
>> id proxy = nil;
>> NSData *addy;
>> NSSocketPort* socket;
>> NSConnection* connection;
>> NSString* hostname;
>>
>> int a;
>> int i;
>>
>> hostname = [sender hostName];
>> socket = (NSSocketPort*)[[NSSocketPortNameServer sharedInstance]
>> portForName:@"BKOtherPort" host:hostname];
>> connection = [NSConnection connectionWithReceivePort: nil
>> sendPort: socket];
>> @try{
>> proxy = [connection rootProxy];
>> }
>> @catch(id exception){
>> proxy = nil;
>>
>> }
>> addy = [socket address];
>> if(proxy){
>> // app level stuff if the proxy exists
>> }
>> }
>>
>> pretty straight forward,
>> and every time I ran it with garbage collection on, the
>> NSConnection initialized, but NEVER returned the proxy. it returned
>> nil.
>> all my instance variables were populated, everything on My end was
>> correct... or at least behaving as expected. it just wouldn't
>> return the proxy object (or the root for that matter)
>> All I did to fix it, was to turn off garbage collection.
>> That part runs like a champ now. the rest of the app won't do
>> anything anymore, as it was built on garbage collection.
>>
>> cheers,
>> -eb
>>
>
> And did you try using CFNetServices instead ? As it is CF based, it
> probably does not have the same GC problem and is it far more easier
> to replace NSNetService by CFNetService than rewriting the whole
> application without GC .
Sorry, it the DO part that does not works, not the Bonjour discovery.
I read it a little to fast. -
On Mon, Jun 30, 2008 at 9:10 PM, eblu <eblugamma...> wrote:> hostname = [sender hostName];
> socket = (NSSocketPort*)[[NSSocketPortNameServer sharedInstance]
> portForName:@"BKOtherPort" host:hostname];
> connection = [NSConnection connectionWithReceivePort: nil sendPort:
> socket];
> @try{
> proxy = [connection rootProxy];
> }
Have you tried the rather more simple:
proxy = [[NSConnection
rootProxyForConnectionWithRegisteredName:@"BKOtherPort" host:[sender
hostname]] retain];
?
Hamish -
El 30/06/2008, a las 19:33, <eblugamma...> escribió:> hey,
> I have a project that uses Bonjour for some of its communication,
> theres a server and a client, and I was having tremendous difficulty
> getting it to work, pouring and pouring over my code, only to
> discover some weeks later that for some odd reason, NSConnections do
> not work when the project is set to support or require garbage
> collection.
>
> As a test I set garbage collection to: Unsupported, and the app
> compiled, and the NSConnection returned the proxy object as
> expected. But the app obviously failed to do much else, because I
> had no retain, release, or autorelease method calls.
>
> I am not a lazy person, but I am a novice programmer, and the
> retain, release, autorelease stuff in cocoa is horrible. I was very
> happy when garbage collection was added to Cocoa, and my projects
> became much easier to develop, and maintain. Now however, I find
> myself with a project riddled with memory problems that did Not
> exist just a few days ago, and as far as I can tell, I don't have
> any choice... My app either gives up Bonjour, or I have to retrofit
> the whole thing to manage its own memory with a system that is, lets
> face it, poorly envisioned.
>
> can anybody shed any light on this? am I really stuck managing the
> memory myself? This is intolerable.
>
> cheers,
> -eblu
> _______________________________________________
>
Well, it always takes a risk to embrace a technology that is just
released, such as garbage collection. Cocoa APIs and the Objective-C
language was not designed from its origins with garbage collection in
mind, so it must have been a really tedious and bug prone process for
Apple to convert all the frameworks to properly work in a managed
memory context. This is unlike Java and C#, which have been created
from the beginning with such feature. In any case, Garbage collection
is neither good or bad, it certainly allows you to not have to care
about Zombies and Leaks, but you will still have to think about the
lifetime of objects. You will still have to remove observers, or
unbind objects, and think that your objects will be eventually
reclaimed by the garbage collector, in an undefined order, so you will
have to implement finalize methods.
From my experience, at the end of the day, coding in a garbage
collected environment, is not such a better deal. Obj-C, unlike C++,
has a very clean way to deal with memory: the release/autorelease/
retain way to manage your memory, along with autorelease pools, and
the consistence of the Cocoa APIs with respect to returned objects,
are heaven compared to what you have to implement in C++, or other not
managed memory languages.
Basically, all you have to do is to always return autoreleased objects
from your methods, and always send release to objects that you created
with alloc or were returned by any method containing "new" or "copy".
Also, if you only use the standard accessor methods for setting
properties, (which send release to the old object and retain the new
one), you will not incur in any memory problem. It should not be that
hard, and at the end your application will potentially perform better,
and for sure it will eat significantly less memory.
Joan Lluch. -
On Wed, Jul 2, 2008 at 6:09 PM, Joan Lluch (casa) <carbonat...> wrote:> Basically, all you have to do is to always return autoreleased objects from
> your methods, and always send release to objects that you created with alloc
> or were returned by any method containing "new" or "copy". Also, if you only
> use the standard accessor methods for setting properties, (which send
> release to the old object and retain the new one), you will not incur in
> any memory problem. It should not be that hard, and at the end your
> application will potentially perform better, and for sure it will eat
> significantly less memory.
I have to object to this bit at the end. It's a common cliche that
garbage collection makes your program perform worse and use more
memory. And certainly this is *possible*. But it's far from a given
these days.
In Cocoa you do lots of retaining and releasing. These operations
aren't free. They involve a lookup into a global hash table and some
sort of atomic increment/decrement operation. They're pretty fast, but
there's certainly some cost there. Garbage collection lets you
eliminate all of this code, so you get a speed bonus there. Whether
this is overcome by the overhead that GC gives you will depend on the
specific program.
The question of memory usage is far from a given, especially in Cocoa
where you do lots of autoreleasing. The pervasive use of autorelease
essentially means that objects don't go away until you go back to the
event loop. This can result in large spikes in memory usage during
event processing. The collector isn't constrained by the event loop
and can run any time it wants to, so it can potentially prevent these
spikes from occurring. This also has a performance impact, as a more
memory efficient program is also a faster program due to having a
smaller working set.
Cocoa's GC also has another interesting advantage: most of the work is
done on a background thread. This means that on any multi-core machine
(which is any Mac sold in the past couple of years) that isn't already
loaded at 100%, this bulk of the GC work essentially comes "for free".
Now, this is not to say that GC will make your program go faster. This
will depend greatly on exactly how your program is designed and
implemented. But while GC won't automatically make your program go
faster, it also won't automatically make it go slower.
Mike -
El 03/07/2008, a las 3:33, Michael Ash escribió:> On Wed, Jul 2, 2008 at 6:09 PM, Joan Lluch (casa) <carbonat...>
> wrote:
>> Basically, all you have to do is to always return autoreleased
>> objects from
>> your methods, and always send release to objects that you created
>> with alloc
>> or were returned by any method containing "new" or "copy". Also, if
>> you only
>> use the standard accessor methods for setting properties, (which send
>> release to the old object and retain the new one), you will not
>> incur in
>> any memory problem. It should not be that hard, and at the end your
>> application will potentially perform better, and for sure it will eat
>> significantly less memory.
>
> I have to object to this bit at the end. It's a common cliche that
> garbage collection makes your program perform worse and use more
> memory. And certainly this is *possible*. But it's far from a given
> these days.
>
> In Cocoa you do lots of retaining and releasing. These operations
> aren't free. They involve a lookup into a global hash table and some
> sort of atomic increment/decrement operation. They're pretty fast, but
> there's certainly some cost there. Garbage collection lets you
> eliminate all of this code, so you get a speed bonus there. Whether
> this is overcome by the overhead that GC gives you will depend on the
> specific program.
>
> The question of memory usage is far from a given, especially in Cocoa
> where you do lots of autoreleasing. The pervasive use of autorelease
> essentially means that objects don't go away until you go back to the
> event loop. This can result in large spikes in memory usage during
> event processing. The collector isn't constrained by the event loop
> and can run any time it wants to, so it can potentially prevent these
> spikes from occurring. This also has a performance impact, as a more
> memory efficient program is also a faster program due to having a
> smaller working set.
>
> Cocoa's GC also has another interesting advantage: most of the work is
> done on a background thread. This means that on any multi-core machine
> (which is any Mac sold in the past couple of years) that isn't already
> loaded at 100%, this bulk of the GC work essentially comes "for free".
>
> Now, this is not to say that GC will make your program go faster. This
> will depend greatly on exactly how your program is designed and
> implemented. But while GC won't automatically make your program go
> faster, it also won't automatically make it go slower.
>
> Mike
>
There is a couple of points I think you are missing here.
First, GC makes programs go slower not because of the overhead of the
garbage collection itself, which I concede that may be comparable to
the retain/release calls in a non-managed environment, but for the
extra memory overhead that it is used. The crucial difference between
a non-managed app and a GC app is that in a non-managed app the memory
is released very soon after the life of an object has expired.
Yes, you are right that Cocoa does produce memory usage peaks (though
you are not fair by not saying that you can eliminate them with a
proper use autorelease pools), but in a GC collection environment
these peaks not only are much bigger, but you don't have any means to
avoid them. The problem with GC is that memory used by the app will
eventually suck all the available resources of the system, and from
this to severe performance issues due to virtual memory page faults,
there is only a very short path. However, all of this problems can be
effectively avoided by the traditional self-managed memory Cocoa
programming. The brain cost of having to set some release/retain
messages here and there, does compensate in my opinion (and
experience) the unknown performance issues that a GC app will have as
it is run in a system with limited memory resources. GC works just
fine if you have lots of memory, but not if you have limited
resources. And know think about why Apple did not implement GC in the
iPhone SDKs.
Joan Lluch (casa) -
Le 4 juil. 08 à 11:25, Joan Lluch (casa) a écrit :>>
>>
>
> There is a couple of points I think you are missing here.
>
> First, GC makes programs go slower not because of the overhead of
> the garbage collection itself, which I concede that may be
> comparable to the retain/release calls in a non-managed environment,
> but for the extra memory overhead that it is used. The crucial
> difference between a non-managed app and a GC app is that in a non-
> managed app the memory is released very soon after the life of an
> object has expired.
>
> Yes, you are right that Cocoa does produce memory usage peaks
> (though you are not fair by not saying that you can eliminate them
> with a proper use autorelease pools), but in a GC collection
> environment these peaks not only are much bigger, but you don't have
> any means to avoid them.
I don't see your point.
Using standard memory management, you can create you own pool, and
using GC, you can manually trigger the GC. What prevent you to avoid
memory peak ?> The problem with GC is that memory used by the app will eventually
> suck all the available resources of the system, and from this to
> severe performance issues due to virtual memory page faults, there
> is only a very short path. However, all of this problems can be
> effectively avoided by the traditional self-managed memory Cocoa
> programming. The brain cost of having to set some release/retain
> messages here and there, does compensate in my opinion (and
> experience) the unknown performance issues that a GC app will have
> as it is run in a system with limited memory resources. GC works
> just fine if you have lots of memory, but not if you have limited
> resources. And know think about why Apple did not implement GC in
> the iPhone SDKs.
You cannot compare iPhone with a desktop/laptop computer. That's not
the only facility Apple has disabled on the iPhone for optimization,
and that does not mean that all thoses features sucks. -
El 04/07/2008, a las 11:38, Jean-Daniel Dupas escribió:> I don't see your point.
>
> Using standard memory management, you can create you own pool, and
> using GC, you can manually trigger the GC. What prevent you to avoid
> memory peak ?
Bonjour, Jean.
Try it yourself. With the correct use of an autorelease pool you can
turn a simple loop to hog all memory resources to not use any
perceivable amount of memory. It is exactly as this because it was
designed this way.
However, this is hardly possible with GC even if you trigger the GC
manually in the middle of the loop. It simply will not release the
memory at the rate you expect because it follows its own rules and as
far as free memory is available it will not free anything unless it is
a reason, according to its algorithms, to do so. Furthermore, the fact
that the memory is released in a parallel threat running at lower
priority, allows for little chance that the memory you would like to
be released at a particular point, actually gets released on time.
When things go really bad, the GC simply stops the app (in a user
noticeable way) until it has been able to free enough resources for
the app to continue. I might add that I really hate this, and this is
why I turned back to retain/release in my Cocoa development.
Look at it the way you want, but you simply won't have control of the
memory resources used by your app in a GC environment. Just keep a
look at Activity Monitor and compare the memory usage of similarly
complex apps, running on GC and not. Usually the difference is huge.
On the other hand, a badly designed "normal" application can
eventually consume a lot more than a GC enabled app. But my point is
that you *can* control it and you can keep it down, however you
*can't* in a GC App. My point is also that it is not *that* hard to
deal manually with your app resources given the clever release/
autorelelase/retain features of Cocoa. However, it is true that GC
will always keep global memory usage in control (in all cases), and
will never crash the app for that reason, because it is obviously
designed to do so.> You cannot compare iPhone with a desktop/laptop computer. That's not
> the only facility Apple has disabled on the iPhone for optimization,
> and that does not mean that all thoses features sucks.
>
Agreed, the iPhone is tiny compared to a desktop computer, and as you
already concede, several features have had to be disabled to keep with
performance requirements. However I didn't want to imply that GC does
suck, It is already a matured technology used by virtually all modern
languages, which can't even be disabled in most of them, and therefore
it is here to stay, and certainly a needed improvement to Cocoa
programming in order to keep with the latest development trends.
Joan Lluch (casa) -
On Jul 4, 2008, at 2:25 AM, Joan Lluch (casa) wrote:> First, GC makes programs go slower not because of the overhead of
> the garbage collection itself, which I concede that may be
> comparable to the retain/release calls in a non-managed environment,
> but for the extra memory overhead that it is used. The crucial
> difference between a non-managed app and a GC app is that in a non-
> managed app the memory is released very soon after the life of an
> object has expired.
Please do not spread misinformation about Objective-C garbage
collection. What you're essentially asserting is that Objective-C
garbage collection will always increase the high-water mark of an
application, and that is not the case.
Memory in a GC app is released very soon after the life of an object
is over -- sometimes, even sooner than it would be under manual memory
management. That's because Objective-C garbage collection runs on a
separate thread.
Under non-GC, an object's memory may not be reclaimed until the
current autorelease pool is drained. However, under GC, an object's
memory can be reclaimed as soon as the collector can tell there are no
more references to it -- no matter when that is.
-- Chris -
On Jul 4, 2008, at 9:32 AM, Joan Lluch (casa) wrote:> However, this is hardly possible with GC even if you trigger the GC
> manually in the middle of the loop. It simply will not release the
> memory at the rate you expect because it follows its own rules and
> as far as free memory is available it will not free anything unless
> it is a reason, according to its algorithms, to do so.
This situation should work. If it does not, that's something to
report to Apple via the bug reporter http://bugreport.apple.com/ and,
if possible attach a test case that demonstrates the issue.
Do not assume that it's simply the collector "follow[ing] its own
rules." The collector should -- and DOES -- collect objects that it
can tell are no longer referenced. In fact, we've had occasional
threads here about objects used at the beginning of a method being
collected while interior pointers that they've handed out are still in
use, so it's not just hypothetical.
-- Chris -
El 04/07/2008, a las 22:54, Chris Hanson escribió:> On Jul 4, 2008, at 2:25 AM, Joan Lluch (casa) wrote:
>
>> First, GC makes programs go slower not because of the overhead of
>> the garbage collection itself, which I concede that may be
>> comparable to the retain/release calls in a non-managed
>> environment, but for the extra memory overhead that it is used. The
>> crucial difference between a non-managed app and a GC app is that
>> in a non-managed app the memory is released very soon after the
>> life of an object has expired.
>
> Please do not spread misinformation about Objective-C garbage
> collection. What you're essentially asserting is that Objective-C
> garbage collection will always increase the high-water mark of an
> application, and that is not the case.
>
> Memory in a GC app is released very soon after the life of an object
> is over -- sometimes, even sooner than it would be under manual
> memory management. That's because Objective-C garbage collection
> runs on a separate thread.
>
> Under non-GC, an object's memory may not be reclaimed until the
> current autorelease pool is drained. However, under GC, an object's
> memory can be reclaimed as soon as the collector can tell there are
> no more references to it -- no matter when that is.
>
> -- Chris
>
Sorry if I my post did not sound appropriate. It never was my
intention. However, let me copy an excerpt of the Cocoa documentation
on the GC algorithm that Cocoa uses. This is *exactly* what the
collector does and this is why the memory used by a GC app is *that*
much compared to a well designed traditional Cocoa app.
<begin excerpt>
The collector is conservativeâit never compacts the heap by moving
blocks of memory and updating pointers. Once allocated, an object
always stays at its original memory location.
The collector is request-driven, not demand-driven. The Cocoa
implementation makes requests at appropriate times. You can also
programmatically request consideration of a garbage collection cycle,
and if a memory threshold has been exceeded a collection is run
automatically.
The collector runs exclusively on the main thread of the application.
At no time are all threads stopped for a collection cycle, and each
thread is stopped for as short a time as is possible. Threads may be
blocked for a short time while all unreachable objects are formed into
the garbage list and weak references zeroed. Only threads that have
directly or indirectly performed an[NSThread self] operation are
subject to garbage collection.
The collector is generational (see âWrite Barriersâ)âmost collections
are very fast and recover significant amounts of recently-allocated
memory, but not all possible memory. Full collections are also fast
and do collect all possible memory, but are run less frequently, at
times unlikely to impact user event processing, and may be aborted in
the presence of new user events.
<end>
Basically for performance reasons, the G. collector "preffers" to let
memory usage grow (while it is still available) in order to avoid too
many collections or to try that the user does not notice it, and in
practice it generally succeeds at it. But you will definitely notice
some annoying sporadic app response delays of half a second or so
specially if your app is very complex and maintains lots of objects
referencing each other in complex graphs.
On the other hand, the documentation also states the following
<begin excerpt>
Garbage collection simplifies memory management and makes it easier to
ensure thread and exception safety. It also avoids common problems
such as retain cycles, and simplifies some code patterns (such as
accessor methods in Cocoa). Together these make applications more
robust.
<end>
Which, of course is also completely true !
Joan Lluch -
On Jul 5, 2008, at 3:00 PM, Joan Lluch (casa) wrote:> However, let me copy an excerpt of the Cocoa documentation on the GCYou haven't updated your documentation since the beginning of November
> algorithm that Cocoa uses.
>
last year.> <begin excerpt>This is not correct for the current implementation of the collector:
> [...]
> The collector runs exclusively on the main thread of the
> application. At no time are all threads stopped for a collection
> cycle, and each thread is stopped for as short a time as is
> possible. Threads may be blocked for a short time while all
> unreachable objects are formed into the garbage list and weak
> references zeroed. Only threads that have directly or indirectly
> performed an[NSThread self] operation are subject to garbage
> collection.
>
The collector is both request and demand driven. The Cocoa
implementation makes requests at appropriate times. You can also
programmatically request consideration of a garbage collection cycle,
and if a memory threshold has been exceeded a collection is run
automatically.
The collector runs on its own thread in the application. At no time
are all threads stopped for a collection cycle, and each thread is
stopped for as short a time as is possible. It is possible for threads
requesting collector actions to block during a critical section on the
collector thread's part. Only threads that have directly or indirectly
performed an [NSThread self] operation are subject to garbage
collection.
The collector is generational (see âWrite Barriersâ)âmost collections
are very fast and recover significant amounts of recently-allocated
memory, but not all possible memory. Full collections are also fast
and do collect all possible memory, but are run less frequently, at
times unlikely to impact user event processing, and may be aborted in
the presence of new user events.
<http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection
/Articles/gcArchitecture.html>
mmalc -
On Jul 5, 2008, at 3:00 PM, Joan Lluch (casa) wrote:> Basically for performance reasons, the G. collector "preffers" to
> let memory usage grow (while it is still available) in order to
> avoid too many collections or to try that the user does not notice
> it, and in practice it generally succeeds at it. But you will
> definitely notice some annoying sporadic app response delays of half
> a second or so specially if your app is very complex and maintains
> lots of objects referencing each other in complex graphs.
Only if some of those objects force the main thread to block (which
can happen as certain objects do require finalization on the main
thread -- these are considered to be bugs and will be eliminated as
time permits) or if you have written classes with complex finalizers
that cause your secondary threads to block.
And it isn't that the collector "prefers" to let memory usage grow.
More so that the collector strives to achieve a balance between memory
use, responsiveness, and CPU cycles consumed, with user responsiveness
being the most heavily weighted of the three.
If there are ways for the collector to collect more efficiently
without impacting user responsiveness, said ways will be implemented....
b.bum -
El 06/07/2008, a las 2:05, mmalc crawford escribió:>
> On Jul 5, 2008, at 3:00 PM, Joan Lluch (casa) wrote:
>
>> However, let me copy an excerpt of the Cocoa documentation on the
>> GC algorithm that Cocoa uses.
>>
> You haven't updated your documentation since the beginning of
> November last year.
>
Oops, thanks. Looks as it is time for me to update things... I will
give the Cocoa GC another try before adding another comment, (my
apologies, I might be testing an earlier version)
By the way, how do I know for sure that a set of documentation
corresponds to a particular version of the SKD. I am using XCode 3.0
for mac development since I believe it is the latest non pre-release
version of the dev-tools and the documentation files that came with
it. Is 3.1 "already" intended for use for mac development?. Since this
post will go out of topic I am also posting this on the Xcode lists,
where I think I should receive a more appropriate answer.
Joan Lluch -
On 7/4/08, Chris Hanson <cmh...> wrote:> Under non-GC, an object's memory may not be reclaimed until the current
> autorelease pool is drained. However, under GC, an object's memory can be
> reclaimed as soon as the collector can tell there are no more references to
> it -- no matter when that is.
I think Joan's point is not that there are circumstances in which the
GC will never reclaim, but that it is not possible to ensure
reclamation deterministically.> From the docs:
--------
collectExhaustively
Tells the receiver to collect iteratively.
- (void)collectExhaustively
Discussion
You use this method to indicate to the collector that it should
perform an exhaustive collection. Collection is subject to
interruption on user input.
Availability Available in Mac OS X v10.5 and later.
Declared In NSGarbageCollector.h
--------
"Collection is subject to interruption on user input" -- with no
mention of when it might be re-started.
Hamish -
El 07/07/2008, a las 0:18, Hamish Allan escribió:> On 7/4/08, Chris Hanson <cmh...> wrote:
>
>> Under non-GC, an object's memory may not be reclaimed until the
>> current
>> autorelease pool is drained. However, under GC, an object's memory
>> can be
>> reclaimed as soon as the collector can tell there are no more
>> references to
>> it -- no matter when that is.
>
> I think Joan's point is not that there are circumstances in which the
> GC will never reclaim, but that it is not possible to ensure
> reclamation deterministically.
>
> [...]
>
> "Collection is subject to interruption on user input" -- with no
> mention of when it might be re-started.
>
> Hamish
Thanks Hamish. That was exactly my point, and that citation of the
documentation gives more plausibility to it.
(my native language is not English, nor I live in an English speaking
country so it can be sometimes difficult for me to express complex
things or to know the more appropriate word to express a concept, this
is why I tend to paraphrase on my writing, and I understand that it
can be difficult to read).
Joan Lluch -
On 7/6/08 11:18 PM, Hamish Allan said:> --------
> collectExhaustively
> Tells the receiver to collect iteratively.
>
> - (void)collectExhaustively
>
> Discussion
> You use this method to indicate to the collector that it should
> perform an exhaustive collection. Collection is subject to
> interruption on user input.
>
> Availability Available in Mac OS X v10.5 and later.
> Declared In NSGarbageCollector.h
> --------
>
> "Collection is subject to interruption on user input" -- with no
> mention of when it might be re-started.
There's always the lower-level:
objc_collect (OBJC_EXHAUSTIVE_COLLECTION |
OBJC_WAIT_UNTIL_DONE);
--
____________________________________________________________
Sean McBride, B. Eng <sean...>
Rogue Research www.rogue-research.com
Mac Software Developer Montréal, Québec, Canada -
On Mon, Jul 7, 2008 at 6:33 PM, Sean McBride <sean...> wrote:> There's always the lower-level:
>
> objc_collect (OBJC_EXHAUSTIVE_COLLECTION |
> OBJC_WAIT_UNTIL_DONE);
If this were called from the main thread, would it guarantee that the
collector run without interruption, given that user input would be
suspended?
Otherwise, there's still rather a difference between "do" and "wait
until done"...
Hamish -
Some minor factual corrections:
On Jul 2, 2008, at 18:33 , Michael Ash wrote:> In Cocoa you do lots of retaining and releasing. These operations
> aren't free. They involve a lookup into a global hash table and some
> sort of atomic increment/decrement operation.
The hash table is only used by NSObject subclasses that do not
implement their own inline reference count. Most Foundation objects
do implement such an inline reference count, so there are no hash
lookups involved, just the atomic increment/decrement. I would
strongly recommend that you implement an inline reference count for
your own objects if they undergo frequent ownership changes.> They're pretty fast, but
> there's certainly some cost there. Garbage collection lets you
> eliminate all of this code, so you get a speed bonus there.
GC does not eliminate this overhead, it replaces it with the overhead
of the write-barrier functions that are called when you do an
assignment. These calls are generated automatically by the compiler,
so you don't see them in your code, but they are still function
calls. This is in addition to the scanning overhead.> The question of memory usage is far from a given, especially in Cocoa
> where you do lots of autoreleasing. The pervasive use of autorelease
> essentially means that objects don't go away until you go back to the
> event loop.
They generally go away whenever you want them to go away. The top of
the event loop is a good default, but it is just that: a convenient
default.> This can result in large spikes in memory usage during
> event processing.
If you see such spikes, simply add autorelease pools to your
processing. Also: don't gratuitously create and/or autorelease
objects if you don't have to. Objective-C object-creation is pretty
heavy-weight compared to other OO languages, regardless of wether you
are using garbage collection or reference counting, so programming-
styles that do a lot of object-creation will suffer, performance-wise.
With RC, there are ways to mitigate the effects ( see http://www.metaobject.com/blog/2007/08/high-performance-objective-c-i.html
and http://www.metaobject.com/blog/2007/09/more-on-mpwobjectcache.html
), whereas I haven't yet found a way to achieve the same effect with
GC.> The collector isn't constrained by the event loop
> and can run any time it wants to, so it can potentially prevent these
> spikes from occurring. This also has a performance impact, as a more
> memory efficient program is also a faster program due to having a
> smaller working set.
You can achieve the same effect by inserting autorelease pools during
processing.> Cocoa's GC also has another interesting advantage: most of the work is
> done on a background thread. This means that on any multi-core machine
> (which is any Mac sold in the past couple of years) that isn't already
> loaded at 100%, this bulk of the GC work essentially comes "for free".
...as does doing the work during the top of the event loop when the
machine is waiting for user input. Of course "free" is actually not
free (a) in terms of power consumption and (b) in terms of memory
bandwidth, which is a shared resource between the cores and frequently
the bottleneck these days and (c) if you have other work for that core.
Cheers,
Marcel -
On Thu, Jul 10, 2008 at 12:17 PM, Marcel Weiher <marcel.weiher...> wrote:> Some minor factual corrections:
>
>
> On Jul 2, 2008, at 18:33 , Michael Ash wrote:
>
>> In Cocoa you do lots of retaining and releasing. These operations
>> aren't free. They involve a lookup into a global hash table and some
>> sort of atomic increment/decrement operation.
>
> The hash table is only used by NSObject subclasses that do not implement
> their own inline reference count. Most Foundation objects do implement such
> an inline reference count, so there are no hash lookups involved, just the
> atomic increment/decrement. I would strongly recommend that you implement
> an inline reference count for your own objects if they undergo frequent
> ownership changes.
Atomic updates are still a pretty big hit on a multiprocessor system
(all of them, these days), and implementing your own is a fair amount
of work that you simply don't have to do in a GC environment.
Especially since you can't reasonably implement it once and share the
implementation, you'll have to manually insert the implementation into
your classes individually.>> They're pretty fast, but
>> there's certainly some cost there. Garbage collection lets you
>> eliminate all of this code, so you get a speed bonus there.
>
> GC does not eliminate this overhead, it replaces it with the overhead of the
> write-barrier functions that are called when you do an assignment. These
> calls are generated automatically by the compiler, so you don't see them in
> your code, but they are still function calls. This is in addition to the
> scanning overhead.
No, it *does* eliminate this overhead, and it has this *other*
overhead. It's not a replacement, because the overheads are not
identical, neither in time nor in location. For example, in the very
common scenario of creating temporary objects which never leave the
stack, a write barrier is never generated.>> The question of memory usage is far from a given, especially in Cocoa
>> where you do lots of autoreleasing. The pervasive use of autorelease
>> essentially means that objects don't go away until you go back to the
>> event loop.
>
> They generally go away whenever you want them to go away. The top of the
> event loop is a good default, but it is just that: a convenient default.
>
>> This can result in large spikes in memory usage during
>> event processing.
>
> If you see such spikes, simply add autorelease pools to your processing.
Yet more work that you don't have to do in a GC system. And
autorelease pools, while quite cheap, are not free.> Also: don't gratuitously create and/or autorelease objects if you don't
> have to. Objective-C object-creation is pretty heavy-weight compared to
> other OO languages, regardless of wether you are using garbage collection or
> reference counting, so programming-styles that do a lot of object-creation
> will suffer, performance-wise.
You can bend your programming style to micro-optimize the language's
speed if you want, but that's not the kind of thing I prefer to do.
I'd rather look at how GC compares to refcounting in typical usage,
not this kind of carefully optimized usage that rarely happens.>> Cocoa's GC also has another interesting advantage: most of the work is
>> done on a background thread. This means that on any multi-core machine
>> (which is any Mac sold in the past couple of years) that isn't already
>> loaded at 100%, this bulk of the GC work essentially comes "for free".
>
> ...as does doing the work during the top of the event loop when the machine
> is waiting for user input.
This doesn't work when you're compute bound, which is of course the
only time that performance actually matters anyway.> Of course "free" is actually not free (a) in
> terms of power consumption and (b) in terms of memory bandwidth, which is a
> shared resource between the cores and frequently the bottleneck these days
> and (c) if you have other work for that core.
That would be why I put it into quotes. The point being that while
it's not really free, it takes no additional wall-clock time in the
common case.
Mike -
On Jul 10, 2008, at 9:50 , Michael Ash wrote:> On Thu, Jul 10, 2008 at 12:17 PM, Marcel Weiher <marcel.weiher...>
>> wrote:
>> [hash tables not generally used + internal refcounts]> Atomic updates are still a pretty big hit on a multiprocessor system
> (all of them, these days),
Yes, they're definitely not free.> and implementing your own is a fair amount
> of work that you simply don't have to do in a GC environment.
Not really, no.> Especially since you can't reasonably implement it once and share the
> implementation, you'll have to manually insert the implementation into
> your classes individually.
There are several ways to share the implementation:
1. Do nothing, CF and Foundation already do it for most of their objects
(and they share their implementation...probably unreasonably...)
2. Implement a common superclass
3. Implement a function, inline function or macro that takes a pointer
to the refcount ivar.>>> They're pretty fast, but
>>> there's certainly some cost there. Garbage collection lets you
>>> eliminate all of this code, so you get a speed bonus there.
>>
>> GC does not eliminate this overhead, it replaces it with the
>> overhead of the
>> write-barrier functions that are called when you do an assignment.
>> These
>> calls are generated automatically by the compiler, so you don't see
>> them in
>> your code, but they are still function calls. This is in addition
>> to the
>> scanning overhead.
>
> It's not a replacement, because the overheads are not
> identical, neither in time nor in location.
Why do you say that? The write barrier code gets called when when you
store an object into an instance variable, same as for a retain. They
are actually at pretty much precisely the same time and location.
I am guessing you are referring to the scanning overhead, about which
you are right: it happens at a different time and in a different
place, and in addition to the checks.> For example, in the very
> common scenario of creating temporary objects which never leave the
> stack, a write barrier is never generated.
Yes, just like objects don't get retained when they are stored in
local variables, that happens when you store them into instance
variables.>> Also: don't gratuitously create and/or autorelease objects if you
>> don't
>> have to. Objective-C object-creation is pretty heavy-weight
>> compared to
>> other OO languages, regardless of wether you are using garbage
>> collection or
>> reference counting, so programming-styles that do a lot of object-
>> creation
>> will suffer, performance-wise.
>
> You can bend your programming style to micro-optimize the language's
> speed if you want, but that's not the kind of thing I prefer to do.
Taking into account the programming style a language supports is about
as far from a micro-optimization as you can get. It is an
architectural concern that informs how you structure your system,
changing it after-the-fact often turns out to be impossible. At least
that's been my experience over the last 20 years or so, YMMV.> I'd rather look at how GC compares to refcounting in typical usage,
> not this kind of carefully optimized usage that rarely happens.
You might have heard about the 80/20 rule, which is actually more a
90/10 or 95/05 rule: most of the execution time is spent in a very
small portion of your code. Being able to go in and *really* optimize
those hotspots actually gives you the most bang for the buck. The
"typical usage", meaning the bulk of the program, generally does not
matter.
This is one of those areas where Objective-C really, really excels:
the ability to combine very high-level, very abstracted code with
small bits of highly optimized code to get an optimum balance of
expressiveness and performance.>> ...as does doing the work during the top of the event loop when the
>> machine
>> is waiting for user input.
>
> This doesn't work when you're compute bound, which is of course the
> only time that performance actually matters anyway.
The latter part is only true iff your apps do not need to responsive.
I prefer apps that are. The first part is where that 95/5 rule comes
in and being able to tune those hotspots really comes in handy.
Cheers,
Marcel -
On Thu, Jul 10, 2008 at 7:33 PM, Marcel Weiher <marcel.weiher...> wrote:>
> On Jul 10, 2008, at 9:50 , Michael Ash wrote:
>
>> On Thu, Jul 10, 2008 at 12:17 PM, Marcel Weiher <marcel.weiher...>
>> wrote:
>>>
>>> [hash tables not generally used + internal refcounts]
>
>> Atomic updates are still a pretty big hit on a multiprocessor system
>> (all of them, these days),
>
> Yes, they're definitely not free.
>
>> and implementing your own is a fair amount
>> of work that you simply don't have to do in a GC environment.
>
> Not really, no.
>
>> Especially since you can't reasonably implement it once and share the
>> implementation, you'll have to manually insert the implementation into
>> your classes individually.
>
> There are several ways to share the implementation:
>
> 1. Do nothing, CF and Foundation already do it for most of their
> objects
> (and they share their implementation...probably unreasonably...)
This, obviously, doesn't work for your own classes.> 2. Implement a common superclass
This doesn't work if you're subclassing something other than NSObject already.> 3. Implement a function, inline function or macro that takes a pointer
> to the refcount ivar.
This works, but still leaves you to copy/paste glue code everywhere.>>>> They're pretty fast, but
>>>> there's certainly some cost there. Garbage collection lets you
>>>> eliminate all of this code, so you get a speed bonus there.
>>>
>>> GC does not eliminate this overhead, it replaces it with the overhead of
>>> the
>>> write-barrier functions that are called when you do an assignment. These
>>> calls are generated automatically by the compiler, so you don't see them
>>> in
>>> your code, but they are still function calls. This is in addition to the
>>> scanning overhead.
>>
>> It's not a replacement, because the overheads are not
>> identical, neither in time nor in location.
>
> Why do you say that? The write barrier code gets called when when you store
> an object into an instance variable, same as for a retain. They are
> actually at pretty much precisely the same time and location.
>
> I am guessing you are referring to the scanning overhead, about which you
> are right: it happens at a different time and in a different place, and in
> addition to the checks.
I'm not referring to the scanning overhead. There are many scenarios
where a refcount modification is made which do not produce a write
barrier in GC-land. For example:
- Temporary objects get a 'release' at the end of their life, and
usually an 'autorelease' at the beginning.
- Paranoid or thread-safe accessors do a retain/autorelease dance
before returning.
- Jumping objects across the end of an autorelease pool by retaining
them before destroying the pool.>> For example, in the very
>> common scenario of creating temporary objects which never leave the
>> stack, a write barrier is never generated.
>
> Yes, just like objects don't get retained when they are stored in local
> variables, that happens when you store them into instance variables.
They do get released though, which is a refcount operation that
doesn't happen in the GC world.>>> Also: don't gratuitously create and/or autorelease objects if you don't
>>> have to. Objective-C object-creation is pretty heavy-weight compared to
>>> other OO languages, regardless of wether you are using garbage collection
>>> or
>>> reference counting, so programming-styles that do a lot of
>>> object-creation
>>> will suffer, performance-wise.
>>
>> You can bend your programming style to micro-optimize the language's
>> speed if you want, but that's not the kind of thing I prefer to do.
>
> Taking into account the programming style a language supports is about as
> far from a micro-optimization as you can get. It is an architectural
> concern that informs how you structure your system, changing it
> after-the-fact often turns out to be impossible. At least that's been my
> experience over the last 20 years or so, YMMV.
I'm not sure I understand what you're saying here. My point is that
ObjC makes it very easy and natural to create temporary objects
without worrying about their lifetimes. In my experience, code which
goes to great lengths to avoid autoreleased objects is messy and much
more bug prone than the "normal" way. Thus, yes, you can avoid many
autoreleased objects if you want, but this is a painful micro
optimization, not the standard way to do things.>> I'd rather look at how GC compares to refcounting in typical usage,
>> not this kind of carefully optimized usage that rarely happens.
>
> You might have heard about the 80/20 rule, which is actually more a 90/10 or
> 95/05 rule: most of the execution time is spent in a very small portion of
> your code. Being able to go in and *really* optimize those hotspots
> actually gives you the most bang for the buck. The "typical usage", meaning
> the bulk of the program, generally does not matter.
I did my master's thesis on high performance code and optimization; I
am more than vaguely familiar with these concepts. My point is merely
that GC can help you without you needing to change your code in any
way. This, to me, is more valuable than peppering my code with lots of
painful manual memory management to make it go faster.> This is one of those areas where Objective-C really, really excels: the
> ability to combine very high-level, very abstracted code with small bits of
> highly optimized code to get an optimum balance of expressiveness and
> performance.
I agree, but I don't agree with your proposed methods. I've optimized
lots of ObjC code in my time, and I've never found it necessary or
even particularly helpful to perform these refcounting or allocation
tricks you discuss. In my experience, object allocation and
refcounting are never hotspots.
Your experience may differ. In fact, I know that it *does* differ,
because we've had that conversation before. But, to be perfectly
frank, your experience is not going to change my mind.>>> ...as does doing the work during the top of the event loop when the
>>> machine
>>> is waiting for user input.
>>
>> This doesn't work when you're compute bound, which is of course the
>> only time that performance actually matters anyway.
>
> The latter part is only true iff your apps do not need to responsive.
This is absolutely not true. If your computation is impacting
responsiveness, then you are compute bound for that constraint, pure
and simple.
Also let's remember the context here. I said that running the GC on a
background thread means that it comes "for free", and you said that
doing work at the top of the event loop is also "for free". It's
obviously *not* for free if there is another pending event waiting to
be processed. In other words, doing a bunch of memory management work
in the event loop is much more likely to impact responsiveness than
doing GC work on a background thread.
Finally, please recall that I never said that GC is better for
everything or that it will provide more performance in all cases. I
only said that GC is not necessarily slower, and that which one is
faster depends greatly on exactly what your application is doing and
how it's written.
Mike -
>> There are several ways to share the implementation:
>>
>> 1. Do nothing, CF and Foundation already do it for most of their
>> objects
>> (and they share their implementation...probably
>> unreasonably...)
>
> This, obviously, doesn't work for your own classes.
But a lot of the objects you will use will tend to be Foundation
objects, especially if you've adopted the temp-object-heavy style.>
>> 2. Implement a common superclass
>
> This doesn't work if you're subclassing something other than
> NSObject already.
Non-NSObject subclasses tend to be things like NSViews which are
fairly heavy-weight and not that temporary.>> 3. Implement a function, inline function or macro that takes a
>> pointer
>> to the refcount ivar.
>
> This works, but still leaves you to copy/paste glue code everywhere.
You don't even have to do that if you don't want to.
Not every solution works in every context, but combined, they cover
the bases rather well, making sharing the implementation quite easy.
In my experience.> [not referring to scanning overhead]
>
> - Temporary objects get a 'release' at the end of their life, and
> usually an 'autorelease' at the beginning.
The cost of a single refcounting op is negligible compared to the cost
of object allocation, so these two are quite irrelevant.> - Jumping objects across the end of an autorelease pool by retaining
> them before destroying the pool.
In my experience, this is rather rare, and the cost once again tends
to be completely negligible compared to the cost of destroying the
pool and the objects in the pool.> - Paranoid or thread-safe accessors do a retain/autorelease dance
> before returning.
This one is actually a problem. Don't do that, it isn't actually
thread-safe and can cause at least as many problems as it 'solves'.>> Yes, just like objects don't get retained when they are stored in
>> local
>> variables, that happens when you store them into instance variables.
>
> They do get released though, which is a refcount operation that
> doesn't happen in the GC world.
Once again, the -release is completely negligible compared to the
actual deallocation.>> Taking into account the programming style a language supports is
>> about as
>> far from a micro-optimization as you can get. It is an architectural
>> concern that informs how you structure your system, changing it
>> after-the-fact often turns out to be impossible. At least that's
>> been my
>> experience over the last 20 years or so, YMMV.
>
> I'm not sure I understand what you're saying here. My point is that
> ObjC makes it very easy and natural to create temporary objects
> without worrying about their lifetimes.
That is exactly my point: this is one case where the comparative ease
is deceptive, as creating lots of temporary objects is not something
that Objective-C supports well. Objective-C is a *hybrid* OO
language, not a pure OO language.> In my experience, code which goes to great lengths to avoid
> autoreleased objects is messy and much
> more bug prone than the "normal" way.
Yes, if it is autoreleasing you avoid, not object creation in the
first place. The extra autorelease only costs you maybe 30%, the
extra object allocation costs you an order of magnitude or more. So
for example, my standard pattern for initialization is something like
this:
-init {
self=[super init];
[self setFoo:[Bar bar]];
return self;
}> Thus, yes, you can avoid many
> autoreleased objects if you want, but this is a painful micro
> optimization, not the standard way to do things.
Once again, avoiding temporary object-creation is not a micro-
optimization, and creating lots of temporary objects is definitely NOT
the standard way to do things in Objective-C.>> You might have heard about the 80/20 rule, which is actually more a
>> 90/10 or
>> 95/05 rule: most of the execution time is spent in a very small
>> portion of
>> your code. Being able to go in and *really* optimize those hotspots
>> actually gives you the most bang for the buck. The "typical
>> usage", meaning
>> the bulk of the program, generally does not matter.
>
> I did my master's thesis on high performance code and optimization; I
> am more than vaguely familiar with these concepts.
Glad to hear that! However, in the sections above you were
continually treating ops that differ in cost by an order of magnitude
or more with equal weight, which makes me somewhat dubious of your
claimed credentials...> My point is merely that GC can help you without you needing to
> change your code in any
> way. This, to me, is more valuable than peppering my code with lots of
> painful manual memory management to make it go faster.
...as does this. Once again: this is not about randomly "peppering"
code with "lots of painful manual memory management", this is about
(a) adopting a coding style that is clean, (b) flows with what
Objective-C provides and is good at and (c) allows for the highly
focused optimizations that actually make an impact, rather than
wasting it on the parts of the code that don't matter.>> This is one of those areas where Objective-C really, really
>> excels: the
>> ability to combine very high-level, very abstracted code with small
>> bits of
>> highly optimized code to get an optimum balance of expressiveness and
>> performance.
>
> I agree, but I don't agree with your proposed methods.
I haven't seen any indication that you know what my proposed methods
are. They certainly do not involve peppering code with micro-
optimizations or> I've optimized lots of ObjC code in my time, and I've never found it
> necessary or
> even particularly helpful to perform these refcounting or allocation
> tricks you discuss. In my experience, object allocation and
> refcounting are never hotspots.
I've given some references to back up my claim...the Postscript
interpreter I wrote in Objective-C is 20x faster because of object-
caching, turning it from laughably slow to competitive with the
industry standard. MPWXmlKit is around 10x faster than other XML
scanners, again due in large part to object caching.> Your experience may differ. In fact, I know that it *does* differ,
> because we've had that conversation before. But, to be perfectly
> frank, your experience is not going to change my mind.
"I have my opinion, who cares about facts or evidence">>>> ...as does doing the work during the top of the event loop when the
>>>> machine
>>>> is waiting for user input.
>>>
>>> This doesn't work when you're compute bound, which is of course the
>>> only time that performance actually matters anyway.
>>
>> The latter part is only true iff your apps do not need to responsive.
>
> This is absolutely not true.
It is.> If your computation is impacting responsiveness, then you are
> compute bound
> for that constraint, pure and simple.
This is true, but it doesn't make your above statement true.> Also let's remember the context here. I said that running the GC on a
> background thread means that it comes "for free", and you said that
> doing work at the top of the event loop is also "for free".
Exactly.> It's obviously *not* for free if there is another pending event
> waiting to
> be processed.
Just like stuff on the background is not free if it ties up bus
bandwidth (which it does, it is scanning after all), or you could have
used the CPU for other means or you are counting power. In two of
those cases, doing the work at the end of the event loop is actually
better than doing it concurrently. And let's remember that once you
are compute-bound, you get much more bang-for-the-buck from targeting
the 5% hot-spot than from off-loading extra work to another thread.> In other words, doing a bunch of memory management work
> in the event loop is much more likely to impact responsiveness than
> doing GC work on a background thread.
That's a strong assertion. Care to back it up with some evidence? My
guess is that the opposite is the case: once the user has her
response from the system, the wait-time to the next user event will
typically be much greater.> Finally, please recall that I never said that GC is better for
> everything or that it will provide more performance in all cases.
Please recall that I never said anything here about the qualities of
GC or RC in general. I just corrected some "minor" factual errors.> I only said that GC is not necessarily slower, and that which one is
> faster depends greatly on exactly what your application is doing and
> how it's written.
Exactly. In Objective-C, a temp-object-heavy style will almost
invariably make your application significantly slower, unless what it
is doing is sufficiently light-weight that it simply doesn't matter.
With the problem there being that when your users start filling your
app with data, what you thought initially would be sufficient will
turn out not to be.
Marcel -
On Fri, Jul 11, 2008 at 3:21 AM, Marcel Weiher <marcel.weiher...> wrote:>> [not referring to scanning overhead]
>>
>> - Temporary objects get a 'release' at the end of their life, and
>> usually an 'autorelease' at the beginning.
>
> The cost of a single refcounting op is negligible compared to the cost of
> object allocation, so these two are quite irrelevant.
A quick test of this claim would appear to disprove it. On my Mac Pro,
an alloc/init/release cycle of NSObject costs about 290ns. Adding an
extra pair of retain/release costs about 480ns. I'm not sure how I can
reasonably measure the object allocation by itself, or the cost of
just retain or just release. But it seems clear that these refcounting
operations are quite significant in cost.>>> Yes, just like objects don't get retained when they are stored in local
>>> variables, that happens when you store them into instance variables.
>>
>> They do get released though, which is a refcount operation that
>> doesn't happen in the GC world.
>
> Once again, the -release is completely negligible compared to the actual
> deallocation.
Apparently not!>>> Taking into account the programming style a language supports is about as
>>> far from a micro-optimization as you can get. It is an architectural
>>> concern that informs how you structure your system, changing it
>>> after-the-fact often turns out to be impossible. At least that's been my
>>> experience over the last 20 years or so, YMMV.
>>
>> I'm not sure I understand what you're saying here. My point is that
>> ObjC makes it very easy and natural to create temporary objects
>> without worrying about their lifetimes.
>
> That is exactly my point: this is one case where the comparative ease is
> deceptive, as creating lots of temporary objects is not something that
> Objective-C supports well. Objective-C is a *hybrid* OO language, not a
> pure OO language.
This disagrees completely with my experience. ObjC supports creating
lots of temporary objects very well. It may not be as *fast* at it as
some languages, but as you say, speed is not a concern in 90% of the
cases. Typical ObjC code creates a great deal of temporary objects,
and this usually works just fine.>> In my experience, code which goes to great lengths to avoid autoreleased
>> objects is messy and much
>> more bug prone than the "normal" way.
>
> Yes, if it is autoreleasing you avoid, not object creation in the first
> place. The extra autorelease only costs you maybe 30%, the extra object
> allocation costs you an order of magnitude or more. So for example, my
> standard pattern for initialization is something like this:
>
> -init {
> self=[super init];
> [self setFoo:[Bar bar]];
> return self;
> }
I don't understand what you're trying to show with this example,
unless it's the fact that you don't avoid autorelease. But then this
directly contradicts what you said before: "Also: don't gratuitously
create and/or autorelease objects if you don't have to." So I'm
confused.>> Thus, yes, you can avoid many
>> autoreleased objects if you want, but this is a painful micro
>> optimization, not the standard way to do things.
>
> Once again, avoiding temporary object-creation is not a micro-optimization,
It certainly is. You're improving the constant in your running time,
not improving the asymptotic algorithmic performance. That's a micro
optimization in my book.> and creating lots of temporary objects is definitely NOT the standard way to
> do things in Objective-C.
It certainly has been in all of the ObjC code I've ever created or
worked with, and most of what I've looked at.>>> You might have heard about the 80/20 rule, which is actually more a 90/10
>>> or
>>> 95/05 rule: most of the execution time is spent in a very small portion
>>> of
>>> your code. Being able to go in and *really* optimize those hotspots
>>> actually gives you the most bang for the buck. The "typical usage",
>>> meaning
>>> the bulk of the program, generally does not matter.
>>
>> I did my master's thesis on high performance code and optimization; I
>> am more than vaguely familiar with these concepts.
>
> Glad to hear that! However, in the sections above you were continually
> treating ops that differ in cost by an order of magnitude or more with equal
> weight, which makes me somewhat dubious of your claimed credentials...
Where did I ever treat anything with equal weight? I don't recall ever
stating or even implying that two different operations were
necessarily equally significant.>> My point is merely that GC can help you without you needing to change your
>> code in any
>> way. This, to me, is more valuable than peppering my code with lots of
>> painful manual memory management to make it go faster.
>
> ...as does this. Once again: this is not about randomly "peppering" code
> with "lots of painful manual memory management", this is about (a) adopting
> a coding style that is clean, (b) flows with what Objective-C provides and
> is good at and (c) allows for the highly focused optimizations that actually
> make an impact, rather than wasting it on the parts of the code that don't
> matter.
Well, you're advocating avoiding object creation and avoiding
autoreleasing objects, which in my experience creates messy,
overly-long, bug-prone code. Points a, b, and c are all fulfilled by
using convenience constructors whenever possible and, in most cases,
not worrying about how many objects you're making.>>> This is one of those areas where Objective-C really, really excels: the
>>> ability to combine very high-level, very abstracted code with small bits
>>> of
>>> highly optimized code to get an optimum balance of expressiveness and
>>> performance.
>>
>> I agree, but I don't agree with your proposed methods.
>
> I haven't seen any indication that you know what my proposed methods are.
> They certainly do not involve peppering code with micro-optimizations or
Seems to me that you've stated them pretty explicitly. Avoid
autorelease, avoid creating objects when you can, reuse objects with
caches when you can't avoid creating them. No?>> I've optimized lots of ObjC code in my time, and I've never found it
>> necessary or
>> even particularly helpful to perform these refcounting or allocation
>> tricks you discuss. In my experience, object allocation and
>> refcounting are never hotspots.
>
> I've given some references to back up my claim...the Postscript interpreter
> I wrote in Objective-C is 20x faster because of object-caching, turning it
> from laughably slow to competitive with the industry standard. MPWXmlKit is
> around 10x faster than other XML scanners, again due in large part to object
> caching.
Yep, well, like I said, your experience differs.>> Your experience may differ. In fact, I know that it *does* differ,
>> because we've had that conversation before. But, to be perfectly
>> frank, your experience is not going to change my mind.
>
> "I have my opinion, who cares about facts or evidence"
That is a gross misrepresentation of what I said. *Your* experience is
not going to override *my* experience.
Now you're just being abusive. Please stop it.
Mike -
On Jul 11, 2008, at 8:59 , Michael Ash wrote:>> The cost of a single refcounting op is negligible compared to the
>> cost of
>> object allocation, so these two are quite irrelevant.
>
> A quick test of this claim would appear to disprove it. On my Mac Pro,
> an alloc/init/release cycle of NSObject costs about 290ns. Adding an
> extra pair of retain/release costs about 480ns. I'm not sure how I can
> reasonably measure the object allocation by itself, or the cost of
> just retain or just release. But it seems clear that these refcounting
> operations are quite significant in cost.
This is the extra refcount table in action, and why inline reference
counts can be such a performance win on objects that are frequently
retained and released.
Changing the object to an NSString (which has an internal reference
count) yields the following results on my MacBook Pro:
retain+release NSString: time per iteration: 67 nanoseconds
Compare this with the times for NSObject, both retain/release and
allocation / deallocation:
retain+release NSObject 2->3 / 3->2 : time per iteration: 223
nanoseconds
retain+release NSObject 1->2 / 2->1 : time per iteration: 276
nanoseconds
alloc+dealloc NSObject: time per iteration: 415 nanoseconds
Cheers,
Marcel
[EOT for me]
[Differences of opinion and experience snipped] -
On Fri, Jul 11, 2008 at 2:17 PM, Marcel Weiher <marcel.weiher...> wrote:>
> On Jul 11, 2008, at 8:59 , Michael Ash wrote:
>
>>> The cost of a single refcounting op is negligible compared to the cost of
>>> object allocation, so these two are quite irrelevant.
>>
>> A quick test of this claim would appear to disprove it. On my Mac Pro,
>> an alloc/init/release cycle of NSObject costs about 290ns. Adding an
>> extra pair of retain/release costs about 480ns. I'm not sure how I can
>> reasonably measure the object allocation by itself, or the cost of
>> just retain or just release. But it seems clear that these refcounting
>> operations are quite significant in cost.
>
> This is the extra refcount table in action, and why inline reference counts
> can be such a performance win on objects that are frequently retained and
> released.
>
> Changing the object to an NSString (which has an internal reference count)
> yields the following results on my MacBook Pro:
>
> retain+release NSString: time per iteration: 67 nanoseconds
>
> Compare this with the times for NSObject, both retain/release and allocation
> / deallocation:
>
> retain+release NSObject 2->3 / 3->2 : time per iteration: 223 nanoseconds
> retain+release NSObject 1->2 / 2->1 : time per iteration: 276 nanoseconds
> alloc+dealloc NSObject: time per iteration: 415 nanoseconds
Seems that NSString and NSMutableString are just faster at everything.
In all cases, the cost of an extra retain/release for them is still
roughly 50% of the cost of an alloc/init/retain. Here are my raw
numbers, times in nanoseconds:
NSObject alloc/init/release 284.3
NSObject alloc/init/retain/release/release 495.7
Extra time taken: 74%
NSString alloc/init/release 40.2
NSString alloc/init/retain/release/release 73.4
Extra time taken: 45%
NSMutableString alloc/init/release 194.7
NSMutableString alloc/init/retain/release/release 300.7
Extra time taken: 54%
I have no explanation as to why NSMutableString is so much slower at
everything. They both end up creating an instance of NSCFString, so
this is puzzling. But there you are.
Mike -
On 7/7/08 11:25 PM, Hamish Allan said:> On Mon, Jul 7, 2008 at 6:33 PM, Sean McBride <sean...> wrote:
>
>> There's always the lower-level:
>>
>> objc_collect (OBJC_EXHAUSTIVE_COLLECTION |
>> OBJC_WAIT_UNTIL_DONE);
>
> If this were called from the main thread, would it guarantee that the
> collector run without interruption, given that user input would be
> suspended?
From it's name, I would think so. But I can't find docs for these
functions (only 4 google hits). Perhaps someone on the obj-c list would
know...
--
____________________________________________________________
Sean McBride, B. Eng <sean...>
Rogue Research www.rogue-research.com
Mac Software Developer Montréal, Québec, Canada -
On Jul 11, 2008, at 12:53 , Michael Ash wrote:>
> Seems that NSString and NSMutableString are just faster at everything.
> In all cases, the cost of an extra retain/release for them is still
> roughly 50% of the cost of an alloc/init/retain. Here are my raw
> numbers, times in nanoseconds:
>
> [timings snipped]>
> I have no explanation as to why NSMutableString is so much slower at
> everything.
I do ;-)> They both end up creating an instance of NSCFString, so
> this is puzzling. But there you are.
These sorts of tests can be tricky to get right: of the two, only the
NSMutableString test is creating strings for you, your NSString test
is just returning the same empty+constant string over and over (you
can verify this by printing the object pointer). You can control for
this, for example by using -initWithCString: with a buffer whose
contents change slightly each time.
These are the numbers I get:
NSMutableString alloc+init(WithCString:) + release(/dealloc) : 1246
nanoseconds
NSMutableString alloc+init(WithCString:) + release(/dealloc) +retain/
release: 1365 nanoseconds
NSString alloc+init(WithCString:) + release(/dealloc) : 418
nanoseconds
NSString alloc+init(WithCString:) + release(/dealloc) +retain/
release: 527 nanoseconds
In both cases, the extra retain/release costs around 110ns, so
allocation is 4x to 10x slower. (I have to admit the 4x surprises me
a little bit, in my experience that value was higher).
The reason NSMutableString is slower is that it needs to allocate an
extra buffer. NSString is pretty heavily optimized, it takes just as
long to allocate as does an empty NSObject:
NSObject alloc+init+release(/dealloc): 417 ns
But of course the retain/release for NSObject is much more expensive:
NSObject alloc+init+release(/dealloc) + retain/release: 740 ns
NSObject alloc+init+release(/dealloc) + 2x retain/release: 1026 ns
So as I said: (a) object allocation slowest (b) out-of-band retain
count slow (c) inline retain count much faster than either.
Regards,
Marcel -
On Sat, Jul 12, 2008 at 2:25 AM, Marcel Weiher <marcel.weiher...> wrote:>
> On Jul 11, 2008, at 12:53 , Michael Ash wrote:
>
>>
>> Seems that NSString and NSMutableString are just faster at everything.
>> In all cases, the cost of an extra retain/release for them is still
>> roughly 50% of the cost of an alloc/init/retain. Here are my raw
>> numbers, times in nanoseconds:
>>
>> [timings snipped]
>
>>
>> I have no explanation as to why NSMutableString is so much slower at
>> everything.
>
> I do ;-)
>
>> They both end up creating an instance of NSCFString, so
>> this is puzzling. But there you are.
>
> These sorts of tests can be tricky to get right: of the two, only the
> NSMutableString test is creating strings for you, your NSString test is just
> returning the same empty+constant string over and over (you can verify this
> by printing the object pointer). You can control for this, for example by
> using -initWithCString: with a buffer whose contents change slightly each
> time.
>
> These are the numbers I get:
>
> NSMutableString alloc+init(WithCString:) + release(/dealloc) : 1246
> nanoseconds
> NSMutableString alloc+init(WithCString:) + release(/dealloc)
> +retain/release: 1365 nanoseconds
> NSString alloc+init(WithCString:) + release(/dealloc) : 418 nanoseconds
> NSString alloc+init(WithCString:) + release(/dealloc) +retain/release: 527
> nanoseconds
>
> In both cases, the extra retain/release costs around 110ns, so allocation
> is 4x to 10x slower. (I have to admit the 4x surprises me a little bit, in
> my experience that value was higher).
>
> The reason NSMutableString is slower is that it needs to allocate an extra
> buffer. NSString is pretty heavily optimized, it takes just as long to
> allocate as does an empty NSObject:
>
> NSObject alloc+init+release(/dealloc): 417 ns
>
> But of course the retain/release for NSObject is much more expensive:
>
> NSObject alloc+init+release(/dealloc) + retain/release: 740 ns
> NSObject alloc+init+release(/dealloc) + 2x retain/release: 1026 ns
>
>
> So as I said: (a) object allocation slowest (b) out-of-band retain count
> slow (c) inline retain count much faster than either.
Well that all makes sense, thanks.
One further question for you, if you will. I got curious and went off
hunting for the inline refcount in NSCFString but couldn't find it.
The closest I got was the '_rc' field in CFRuntimeBase, but it's
inside an #if __LP64__ clause, so we don't get it in normal code these
days. The __CFString struct doesn't seem to have any place to store a
refcount. Am I missing something here, or does it only have an inline
refcount in 64-bit?
Mike -
On Jul 12, 2008, at 8:25 AM, Michael Ash wrote:> On Sat, Jul 12, 2008 at 2:25 AM, Marcel Weiher <marcel.weiher...>
>> wrote:
>>
>> So as I said: (a) object allocation slowest (b) out-of-band
>> retain count
>> slow (c) inline retain count much faster than either.
>
> Well that all makes sense, thanks.
You're very welcome :-)> One further question for you, if you will. I got curious and went off
> hunting for the inline refcount in NSCFString but couldn't find it.
Yeah, NSCFString doesn't actually declare any of "its" instance
variables, which are actually those of the private CFString structure
it uses.> The closest I got was the '_rc' field in CFRuntimeBase, but it's
> inside an #if __LP64__ clause, so we don't get it in normal code these
> days. The __CFString struct doesn't seem to have any place to store a
> refcount. Am I missing something here, or does it only have an inline
> refcount in 64-bit?
No, the inline reference count is available for all CF objects, and
not limited to 64 bit.
What version of the structure are you looking at? For example http://www.cocoadev.com/index.pl?HowToCreateTollFreeBridgedClass
shows this version, which matches what I got from opensource.apple.com
/* All CF "instances" start with this structure. Never refer to
* these fields directly -- they are for CF's use and may be added
* to or removed or change format without warning. Binary
* compatibility for uses of this struct is not guaranteed from
* release to release.
*/
typedef struct __CFRuntimeBase {
void *_isa;
#if defined(__ppc__)
uint16_t _rc;
uint16_t _info;
#elif defined(__i386__)
uint16_t _info;
uint16_t _rc;
#else
#error unknown architecture
#endif
} CFRuntimeBase;
Cheers,
Marcel -
Marcel Weiher wrote:>
> uint16_t _rc;
Oh, the horror of it all! Only 65,535 objects can retain a string!!!
What am I to do when I model all the citizens of the US voting for just
one presidential candidate in November, and the retain count overflows?!?!
Okay, that's not a real concern for me, but that's the kind of thing
where you find hanging chads are the least of your worries. Didn't
someone famous once say, "Who needs more than 64 KB in a computer?" ;-) -
On Sat, Jul 12, 2008 at 1:24 PM, Marcel Weiher <marcel.weiher...> wrote:> No, the inline reference count is available for all CF objects, and not
> limited to 64 bit.
> What version of the structure are you looking at? For example
> http://www.cocoadev.com/index.pl?HowToCreateTollFreeBridgedClass shows this
> version, which matches what I got from opensource.apple.com
I was looking at:
http://www.opensource.apple.com/darwinsource/projects/apsl/CF-476.10/CFRunt
ime.h
It defines:
typedef struct __CFRuntimeBase {
uintptr_t _cfisa;
uint8_t _cfinfo[4];
#if __LP64__
uint32_t _rc;
#endif
} CFRuntimeBase;
I guess this isn't the right one, then.
To Gary, about 16-bit refcounts, I'd imagine that there's some logic
in there where if you hit 0xFFFF, it considers that to be a flag to
use an external refcount instead, at the cost of some speed.
Mike -
It looks like it was expanded in 10.5, then, but 10.4 and prior use the
16-bit variables. Regardless of whether it handles overflows or not, my
little joke was meant to illustrate that too many developers (generally
speaking) think that resources (generally speaking) are unlimited and
put no thought into what to do if an error occurs or how to handle
requirements that are potentially huge. I'm not saying those on this
list think this way, but when people say that there's no cost to this or
that approach, I just want to say, "Get thee a computer science degree!"
Michael Ash wrote:> On Sat, Jul 12, 2008 at 1:24 PM, Marcel Weiher <marcel.weiher...> wrote:
>> No, the inline reference count is available for all CF objects, and not
>> limited to 64 bit.
>> What version of the structure are you looking at? For example
>> http://www.cocoadev.com/index.pl?HowToCreateTollFreeBridgedClass shows this
>> version, which matches what I got from opensource.apple.com
>
> I was looking at:
>
> http://www.opensource.apple.com/darwinsource/projects/apsl/CF-476.10/CFRunt
ime.h
>
> It defines:
>
> typedef struct __CFRuntimeBase {
> uintptr_t _cfisa;
> uint8_t _cfinfo[4];
> #if __LP64__
> uint32_t _rc;
> #endif
> } CFRuntimeBase;
>
> I guess this isn't the right one, then.
>
> To Gary, about 16-bit refcounts, I'd imagine that there's some logic
> in there where if you hit 0xFFFF, it considers that to be a flag to
> use an external refcount instead, at the cost of some speed.
>
> Mike -
On Jul 12, 2008, at 13:42 , Michael Ash wrote:>
> http://www.opensource.apple.com/darwinsource/projects/apsl/CF-476.10/CFRunt
ime.h
>
> typedef struct __CFRuntimeBase {
> uintptr_t _cfisa;
> uint8_t _cfinfo[4];
> #if __LP64__
> uint32_t _rc;
> #endif
> } CFRuntimeBase;
>
> I guess this isn't the right one, then.
If you look at the corresponding CFRuntime.c file, I think you'll find
that there is logic there for treating part of the _cfinfo as a retain
count (look for _CFRetain() )> To Gary, about 16-bit refcounts, I'd imagine that there's some logic
> in there where if you hit 0xFFFF, it considers that to be a flag to
> use an external refcount instead, at the cost of some speed.
Yep. Inline reference counts are an optimization, as such they need
to cater to the common case, not to the outliers (which still have to
be handled correctly, but don't need to be as fast).
Cheers,
Marcel -
On Sun, Jul 13, 2008 at 12:29 PM, Marcel Weiher <marcel.weiher...> wrote:>http://www.opensource.apple.com/darwinsource/projects/apsl/CF-476.10/CFRunt
> On Jul 12, 2008, at 13:42 , Michael Ash wrote:
>
ime.h>>
>> typedef struct __CFRuntimeBase {
>> uintptr_t _cfisa;
>> uint8_t _cfinfo[4];
>> #if __LP64__
>> uint32_t _rc;
>> #endif
>> } CFRuntimeBase;
>>
>> I guess this isn't the right one, then.
>
> If you look at the corresponding CFRuntime.c file, I think you'll find that
> there is logic there for treating part of the _cfinfo as a retain count
> (look for _CFRetain() )
So it does. That'll teach me to take CF structures at face value. Thanks.
Mike


