Re: Garbage Collection woes...

2008-06-29 Thread j o a r


On Jun 27, 2008, at 12:31 PM, John Engelhart wrote:

Lesson #1:  If you have any interest in performance, you must avoid,  
at all costs, "writing" to a __strong pointer.



That's almost like saying:

	"If you have any interest in performance, you must avoid, at all  
costs, using a high level language like C."


It's only true if you ignore other things that are also both true, and  
that many consider to have a higher priority. For one, using higher  
level languages and libraries allows us to be more productive and to  
build software that delivers more functionality while at the same time  
being easier to maintain.




-(BOOL)doSomething:(id)obj error:(NSError **)error
{
 if(error != NULL) { *error = NULL; } // Make sure we clear the  
error object

}

It's sort of ambiguous as to what should be returned by the indirect  
error pointer on the condition of success.  I could think of several  
neat ideas if the expected behavior were defined up front, even  
requiring the caller to initialize the pointer to a default NSError  
singleton and allowing errors to accumulate in a stack like  
fashion.  Alas, the only clearly defined behavior is that one  
failure, a NSError object is indirectly returned.



The relevant documentation is found here:




If you have suggestions for improvements, please file formal  
enhancement requests.



The write barrier penalty is substantial.  I benchmarked a tight  
loop that called a function that did nothing but the naive clearing  
of the value.  The result (on a 1.5GHz G4) was that it was 2429.55%  
(or, over 24 times) slower with -fobjc-gc enabled.  So, best to  
avoid updating a __strong pointer at any and all costs.



If you're a developer who cares about performance, your #1 priority is  
to implement the actual functionality. Your #2 priority to run  
meaningful benchmarks. Your #3 priority to address any performance  
problems that you find, in order of severity.


Synthetic / micro benchmarks are typically of limited value. Most  
Cocoa application developers will find that their performance problems  
are higher level and more algorithmic in nature, rather than on this  
very low level.




typedef struct {
 NSString *aString;
 __weak NSString *aWeakString;
 NSInteger anInteger;
} MYStructType;

MYStructType globalStructTypeArray[42]; // <-- Global!


The pointer to 'aString' in the above (or any of my other __strong  
pointers in my actual code) were clearly not being treated as  
__strong, and the GC system was reclaiming them causing all sorts of  
fun and random crashes.




Bugs are bad, and if you think that you have found one, it would be  
great if you could file a formal bug report.
That said, this is Cocoa-Dev, and the code above has very little to do  
with Cocoa / Objective-C development in practice.
Most Cocoa developers will find that Garbage Collection works  
absolutely fine, and that runtime performance is about the same  
(sometimes better, sometimes worse) compared with using manual memory  
management.


j o a r


___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]


Re: Garbage Collection woes...

2008-06-29 Thread mmalc crawford


On Jun 27, 2008, at 12:31 PM, John Engelhart wrote:


-(BOOL)doSomething:(id)obj error:(NSError **)error
{
 if(error != NULL) { *error = NULL; } // Make sure we clear the  
error object

}


Why are you doing this?

It's sort of ambiguous as to what should be returned by the indirect  
error pointer on the condition of success.  I could think of several  
neat ideas if the expected behavior were defined up front, even  
requiring the caller to initialize the pointer to a default NSError  
singleton and allowing errors to accumulate in a stack like  
fashion.  Alas, the only clearly defined behavior is that one  
failure, a NSError object is indirectly returned.



That's exactly what's expected.
"In general, a method should signal an error condition by—for example— 
returning NO or nilrather than by the simple presence of an error  
object. The method can then optionally return an NSError object by  
reference, in order to further describe the error."


mmalc

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]


Re: Garbage Collection woes...

2008-06-28 Thread Stephen J. Butler
On Fri, Jun 27, 2008 at 2:31 PM, John Engelhart
<[EMAIL PROTECTED]> wrote:
> Lesson #2:  Since there is so little documentation about the GC system, this
> involves a lot of speculation, but I think it summarizes what's really going
> on.  This all started with an effort to keep a __weak reference to a passed
> in string that was used to initialize an element in a cache.  When the cache
> was checked, if that weak reference was NULL, then the cache line is invalid
> and should be cleared.  The cache consisted of a global array of elements,
> selection was done via KEY_STRING_HASH % CACHE_SIZE, and everything was
> under a mutex lock.  An approximation of the cache is:
>
> typedef struct {
>  NSString *aString;
>  __weak NSString *aWeakString;
>  NSInteger anInteger;
> } MYStructType;
>
> MYStructType globalStructTypeArray[42]; // <-- Global!
>
> Simple, right?  That's how it always starts out...  The first problem
> encountered was:
>
> [EMAIL PROTECTED] /tmp% gcc -o Global_GC Global_GC.m -framework Foundation
> -fobjc-gc
> Global_GC.m:14: warning: __weak attribute cannot be specified on a field
> declaration
>
> (The attached file contains the full example demonstrating the problem.)
>
> I'm not really sure what this means, and I don't recall reading anything in
> the documentation that would suggest anything is amiss.  I never actually
> managed to figure out what, if any, problem this causes because it quickly
> became apparent that there was a much bigger problem that needed dealing
> with:

Speculation: __weak needs a read-barrier as well as a write-barrier,
and with structs people have a long history of reading them without
going through the accessor. This isn't generally a problem for
__strong and write barriers because for all of this to work you need
to make sure that the memory for MYStructType is GC scanned anyway.

> The pointer to 'aString' in the above (or any of my other __strong pointers
> in my actual code) were clearly not being treated as __strong, and the GC
> system was reclaiming them causing all sorts of fun and random crashes.
>
> The documentation states: The initial root set of objects is comprised of
> global variables, stack variables, and objects with external references.
> These objects are never considered as garbage.

This is kind of a lie since not ALL global memory is treated as
collectable. Hence the need for special assigns.

> Putting the pieces together, it became obvious what was really going on.
>  The two commented out lines in the example that update the global variable
> are the key to the mystery and make everything work as expected.
>
> It turns out that when the documentation says that "root set of objects is
> comprised of global variables", it's true, but probably not in the way that
> you think it is.
>
> It would 'seem' that global variables are only __strong when the compiler
> can reason that you're referring to a global variable directly. In this
> particular case, that would be:
>
> globalStructTypeArray[23].aString = newString;

Speculation: another way to think of it is that not all global memory
is considered a collectable root until you've first used it. That is,
on the first call to objc_assign_global, the pointer is added to the
list of collectable roots. It appears to be a lazy sort of system.

> They are not strong when you refer to them indirectly (even though write
> barriers are clearly being performed), such as:
>
> update(&globalStructTypeArray[23], newString);
>
> update(MYStructType *aStructType, NSString *string) {
>  aStructType->aString = string;
> }
>
> Looking at the assembly output, the reason becomes clear:
>
> The write barrier used by the first, direct reference is objc_assign_global,
> while the write barrier used by the indirect reference in update is
> objc_assign_strongCast.
>
> This is probably an important point that you should consider if you're
> depending on global variables being truly __strong.  No doubt someone here
> will explain that this isn't a bug, it's just that you shouldn't reference a
> global variable via a pointer (this is sarcastic for the challenged).

You shouldn't reference a global variable via a pointer! Kidding.

The problem is essentially the same as the one in this code:

class Foo {
  public:
  NSString* fieldA;
  int fieldB;

  Foo( NSString *_fieldA, int _fieldB ) : fieldA( _fieldA ), fieldB(
_fieldB ) {}
};

Foo *f = new Foo( @"Something strong", 42 );

IIRC, you'll also find that here f->fieldA is collected way before you
expect. Only this time, there's plenty of emails about how to fix it.
The problem is that ::new returns a block of non-GC memory. So even
though the write barriers are setup properly, f->fieldA is in a
non-scanned region. See here:

http://lists.apple.com/archives/Cocoa-dev/2008/Feb/msg00435.html

In your case, globalStructTypeArray is also in a non-scanned region,
which is why the compiler uses the special _global assign. But you've
hidden the global nature from the compiler

Ask for help when encountering GC issues (was Re: Garbage Collection woes...)

2008-06-28 Thread Chris Hanson

On Jun 27, 2008, at 12:31 PM, John Engelhart wrote:

Lesson #1:  If you have any interest in performance, you must avoid,  
at all costs, "writing" to a __strong pointer.


If this were the case, all assignments to instance variables would be  
exceptionally costly under GC.  They are not -- applications run under  
GC can get quite good performance.  Assignment through write barriers  
does have a cost, yes, but that cost is amortized by the fact that the  
GC system can use the generation information provided by using write  
barriers to be more efficient.


Furthermore *every* assignment to an object pointer is effectively a  
__strong assignment.  If your performance assumptions were true in the  
general case, you might expect to see everything that runs under GC  
perform at a fraction the speed it runs under non-GC, which isn't the  
case for the large application I work on.


And thus began yet another exciting adventure with the GC system  
that caused me to waste several days.


You could ask for help on the list sooner rather than spending days  
and days beating your head against something like this...


I'll reiterate what I've said in the past, though:  Objective-C  
Garbage Collection works quite well in Leopard, and applications large  
and small can use it quite effectively.  I generally don't create new  
Cocoa projects these days without turning on Objective-C GC support.   
If you encounter bugs, file them.


  -- Chris

___

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

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:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]


Garbage Collection woes...

2008-06-28 Thread John Engelhart
A few days ago, I decided to give leopards GC system another crack.   
The experience was pretty much the same as all my other experiences  
have been with Leopards GC system (several days wasted).  I learned  
two important things that I thought I would share:


Lesson #1:  If you have any interest in performance, you must avoid,  
at all costs, "writing" to a __strong pointer.  I thought I was being  
quite careful about which pointers were __strong and which were  
getting touched in the critical path, which should have been none.  
However, once I flipped GC on, micro benchmarks results went right  
through the floor, an order of magnitude worse.  The problem was the  
following innocent statement:


-(BOOL)doSomething:(id)obj error:(NSError **)error
{
  if(error != NULL) { *error = NULL; } // Make sure we clear the  
error object

}

It's sort of ambiguous as to what should be returned by the indirect  
error pointer on the condition of success.  I could think of several  
neat ideas if the expected behavior were defined up front, even  
requiring the caller to initialize the pointer to a default NSError  
singleton and allowing errors to accumulate in a stack like fashion.   
Alas, the only clearly defined behavior is that one failure, a NSError  
object is indirectly returned.


I prefer the "Initialize your environment to a known state."   Leave  
it up to the optimizer to figure out if such an initialization is in  
fact useless because, one way or another, some path is guaranteed to  
set it later on without depending on its initial value.


Now, typically, the passed in error pointer itself lives on the  
stack.  The compiler can't tell where the pointer really lives, and so  
it must "assume the worst"[1] an insert a write barrier, even if such  
a write barrier is pointless because it's protecting an update to a  
location that ultimately lives on the stack.


[1] This is an important point in the next GC lesson learned.

So the way to 'fix' this is to do something like:

  if((error != NULL) && (*error != NULL)) { *error = NULL; }

This will at least only incur a write barrier penalty only when it  
needs to.


The write barrier penalty is substantial.  I benchmarked a tight loop  
that called a function that did nothing but the naive clearing of the  
value.  The result (on a 1.5GHz G4) was that it was 2429.55% (or, over  
24 times) slower with -fobjc-gc enabled.  So, best to avoid updating a  
__strong pointer at any and all costs.


Lesson #2:  Since there is so little documentation about the GC  
system, this involves a lot of speculation, but I think it summarizes  
what's really going on.  This all started with an effort to keep a  
__weak reference to a passed in string that was used to initialize an  
element in a cache.  When the cache was checked, if that weak  
reference was NULL, then the cache line is invalid and should be  
cleared.  The cache consisted of a global array of elements, selection  
was done via KEY_STRING_HASH % CACHE_SIZE, and everything was under a  
mutex lock.  An approximation of the cache is:


typedef struct {
  NSString *aString;
  __weak NSString *aWeakString;
  NSInteger anInteger;
} MYStructType;

MYStructType globalStructTypeArray[42]; // <-- Global!

Simple, right?  That's how it always starts out...  The first problem  
encountered was:


[EMAIL PROTECTED] /tmp% gcc -o Global_GC Global_GC.m -framework  
Foundation -fobjc-gc
Global_GC.m:14: warning: __weak attribute cannot be specified on a  
field declaration


(The attached file contains the full example demonstrating the problem.)

I'm not really sure what this means, and I don't recall reading  
anything in the documentation that would suggest anything is amiss.  I  
never actually managed to figure out what, if any, problem this causes  
because it quickly became apparent that there was a much bigger  
problem that needed dealing with:


The pointer to 'aString' in the above (or any of my other __strong  
pointers in my actual code) were clearly not being treated as  
__strong, and the GC system was reclaiming them causing all sorts of  
fun and random crashes.


The documentation states: The initial root set of objects is comprised  
of global variables, stack variables, and objects with external  
references. These objects are never considered as garbage.


And thus began yet another exciting adventure with the GC system that  
caused me to waste several days.  In all honesty, I've probably sunk  
about 3 weeks worth of 10 hour days in to tracking down "problems"  
like this whenever I've tried to use the GC system.  At this point,  
I've probably spent more time dealing with memory allocation problems  
due to the GC system than I've spent dealing with memory allocation  
problems in the last 20 years.


I spent several hours digging through the assembly output, and even  
(once again) plunging in to the gcc source to try to figure out what  
was going on.  Using dtrace to catch all calls t